From 1b5217985b86793d558a6d107099247570c8b557 Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 5 May 2020 17:35:39 -0700 Subject: [PATCH 01/13] notebook document creation in UI side --- .../src/notebookTestMain.ts | 29 ++- .../api/browser/mainThreadNotebook.ts | 100 ++++++--- .../workbench/api/common/extHost.protocol.ts | 27 ++- .../workbench/api/common/extHostNotebook.ts | 195 ++++++++++++++---- .../notebook/browser/notebookEditorInput.ts | 4 + .../notebook/browser/notebookServiceImpl.ts | 45 +++- .../common/model/notebookTextModel.ts | 48 +++++ .../contrib/notebook/common/notebookCommon.ts | 9 +- .../notebook/common/notebookEditorModel.ts | 63 +++++- .../notebook/common/notebookService.ts | 9 +- .../api/extHostNotebookConcatDocument.test.ts | 6 +- 11 files changed, 442 insertions(+), 93 deletions(-) diff --git a/extensions/vscode-notebook-tests/src/notebookTestMain.ts b/extensions/vscode-notebook-tests/src/notebookTestMain.ts index b27f6760475..e5402feb298 100644 --- a/extensions/vscode-notebook-tests/src/notebookTestMain.ts +++ b/extensions/vscode-notebook-tests/src/notebookTestMain.ts @@ -6,12 +6,22 @@ import * as vscode from 'vscode'; export function activate(context: vscode.ExtensionContext): any { - context.subscriptions.push(vscode.notebook.registerNotebookProvider('notebookCoreTest', { - resolveNotebook: async (editor: vscode.NotebookEditor) => { - await editor.edit(eb => { - eb.insert(0, 'test', 'typescript', vscode.CellKind.Code, [], {}); - }); - return; + context.subscriptions.push(vscode.notebook.registerNotebookContentProvider('notebookCoreTest', { + onDidChangeNotebook: new vscode.EventEmitter().event, + openNotebook: async (_resource: vscode.Uri) => { + return { + languages: ['typescript'], + metadata: {}, + cells: [ + { + source: 'test', + language: 'typescript', + cellKind: vscode.CellKind.Code, + outputs: [], + metadata: {} + } + ] + }; }, executeCell: async (_document: vscode.NotebookDocument, _cell: vscode.NotebookCell | undefined, _token: vscode.CancellationToken) => { if (!_cell) { @@ -26,8 +36,11 @@ export function activate(context: vscode.ExtensionContext): any { }]; return; }, - save: async (_document: vscode.NotebookDocument) => { - return true; + saveNotebook: async (_document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => { + return; + }, + saveNotebookAs: async (_targetResource: vscode.Uri, _document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => { + return; } })); } diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 4653ef3ffec..031d901d8c5 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -8,7 +8,7 @@ import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IEx import { Disposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, CellKind, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, CellKind, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -84,7 +84,9 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo registerListeners() { this._register(this._notebookService.onDidChangeActiveEditor(e => { - this._proxy.$updateActiveEditor(e.viewType, e.uri); + this._proxy.$acceptDocumentAndEditorsDelta({ + newActiveEditor: e.uri + }); })); const updateOrder = () => { @@ -116,8 +118,8 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo this._notebookService.unregisterNotebookRenderer(handle); } - async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise { - let controller = new MainThreadNotebookController(this._proxy, this, viewType); + async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, v2: boolean): Promise { + let controller = new MainThreadNotebookController(this._proxy, this, viewType, v2); this._notebookProviders.set(viewType, controller); this._notebookService.registerNotebookController(viewType, extension, controller); return; @@ -129,11 +131,11 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo return; } - async $createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise { + async $_deprecated_createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise { let controller = this._notebookProviders.get(viewType); if (controller) { - controller.createNotebookDocument(handle, viewType, resource); + controller._deprecated_createNotebookDocument(handle, viewType, resource); } return; @@ -163,8 +165,8 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo } } - async resolveNotebook(viewType: string, uri: URI): Promise { - let handle = await this._proxy.$resolveNotebook(viewType, uri); + async _deprecated_resolveNotebook(viewType: string, uri: URI): Promise { + let handle = await this._proxy.$_deprecated_resolveNotebook(viewType, uri); return handle; } @@ -195,15 +197,50 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo export class MainThreadNotebookController implements IMainNotebookController { private _mapping: Map = new Map(); + static documentHandle: number = 0; constructor( private readonly _proxy: ExtHostNotebookShape, private _mainThreadNotebook: MainThreadNotebooks, - private _viewType: string + private _viewType: string, + public readonly v2: boolean ) { } - async resolveNotebook(viewType: string, uri: URI): Promise { + async createNotebook(viewType: string, uri: URI, forBackup: boolean): Promise { + let mainthreadNotebook = this._mapping.get(URI.from(uri).toString()); + + if (mainthreadNotebook) { + return mainthreadNotebook.textModel; + } + + let document = new MainThreadNotebookDocument(this._proxy, MainThreadNotebookController.documentHandle++, viewType, uri); + await this.createNotebookDocument(document.handle, document.viewType, document.uri); + + if (forBackup) { + return document.textModel; + } + + // open notebook document + const data = await this._proxy.$resolveNotebookData(viewType, uri); + if (!data) { + return; + } + + document.textModel.languages = data.languages; + document.textModel.metadata = data.metadata; + document.textModel.applyEdit(document.textModel.versionId, [ + { + editType: CellEditType.Insert, + index: 0, + cells: data!.cells + } + ]); + + return document.textModel; + } + + async _deprecated_resolveNotebook(viewType: string, uri: URI): Promise { // TODO: resolve notebook should wait for all notebook document destory operations to finish. let mainthreadNotebook = this._mapping.get(URI.from(uri).toString()); @@ -211,7 +248,7 @@ export class MainThreadNotebookController implements IMainNotebookController { return mainthreadNotebook.textModel; } - let notebookHandle = await this._mainThreadNotebook.resolveNotebook(viewType, uri); + let notebookHandle = await this._mainThreadNotebook._deprecated_resolveNotebook(viewType, uri); if (notebookHandle !== undefined) { mainthreadNotebook = this._mapping.get(URI.from(uri).toString()); if (mainthreadNotebook && mainthreadNotebook.textModel.cells.length === 0) { @@ -250,10 +287,35 @@ export class MainThreadNotebookController implements IMainNotebookController { this._proxy.$onDidReceiveMessage(uri, message); } - // Methods for ExtHost async createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise { let document = new MainThreadNotebookDocument(this._proxy, handle, viewType, URI.revive(resource)); this._mapping.set(URI.revive(resource).toString(), document); + + await this._proxy.$acceptDocumentAndEditorsDelta({ + addedDocuments: [{ + viewType: viewType, + handle: document.handle, + uri: resource + }] + }); + } + + async removeNotebookDocument(notebook: INotebookTextModel): Promise { + let document = this._mapping.get(URI.from(notebook.uri).toString()); + + if (!document) { + return; + } + + await this._proxy.$acceptDocumentAndEditorsDelta({ removedDocuments: [notebook.uri] }); + document.dispose(); + this._mapping.delete(URI.from(notebook.uri).toString()); + } + + // Methods for ExtHost + async _deprecated_createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise { + let document = new MainThreadNotebookDocument(this._proxy, handle, viewType, URI.revive(resource)); + this._mapping.set(URI.revive(resource).toString(), document); } updateLanguages(resource: UriComponents, languages: string[]) { @@ -280,20 +342,6 @@ export class MainThreadNotebookController implements IMainNotebookController { return this._proxy.$executeNotebook(this._viewType, uri, handle, token); } - async destoryNotebookDocument(notebook: INotebookTextModel): Promise { - let document = this._mapping.get(URI.from(notebook.uri).toString()); - - if (!document) { - return; - } - - let removeFromExtHost = await this._proxy.$destoryNotebookDocument(this._viewType, notebook.uri); - if (removeFromExtHost) { - document.dispose(); - this._mapping.delete(URI.from(notebook.uri).toString()); - } - } - async save(uri: URI, token: CancellationToken): Promise { return this._proxy.$saveNotebook(this._viewType, uri, token); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index c6b3c31999d..942e16bc90c 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 { INotebookMimeTypeSelector, IOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookMimeTypeSelector, IOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto } 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'; @@ -687,11 +687,11 @@ export type NotebookCellOutputsSplice = [ ]; export interface MainThreadNotebookShape extends IDisposable { - $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise; + $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, v2: boolean): Promise; $unregisterNotebookProvider(viewType: string): Promise; $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise; $unregisterNotebookRenderer(handle: number): Promise; - $createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise; + $_deprecated_createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise; $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise; $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise; $updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise; @@ -1538,16 +1538,31 @@ export interface INotebookEditorPropertiesChangeData { selections: INotebookSelectionChangeEvent | null; } +export interface INotebookModelAddedData { + uri: UriComponents; + handle: number; + // versionId: number; + viewType: string; +} + +export interface INotebookDocumentsAndEditorsDelta { + removedDocuments?: UriComponents[]; + addedDocuments?: INotebookModelAddedData[]; + // removedEditors?: string[]; + // addedEditors?: ITextEditorAddData[]; + newActiveEditor?: UriComponents | null; +} + export interface ExtHostNotebookShape { - $resolveNotebook(viewType: string, uri: UriComponents): Promise; + $resolveNotebookData(viewType: string, uri: UriComponents): Promise; + $_deprecated_resolveNotebook(viewType: string, uri: UriComponents): Promise; $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise; $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise; - $updateActiveEditor(viewType: string, uri: UriComponents): Promise; - $destoryNotebookDocument(viewType: string, uri: UriComponents): Promise; $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void; $onDidReceiveMessage(uri: UriComponents, message: any): void; $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void; $acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void; + $acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): Promise; } export interface ExtHostStorageShape { diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 1b1d7db1f80..2b8e61ca2c0 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -10,10 +10,10 @@ import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecyc import { ISplice } from 'vs/base/common/sequence'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { CellKind, CellOutputKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData } from 'vs/workbench/api/common/extHost.protocol'; +import { CellKind, CellOutputKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData, INotebookDocumentsAndEditorsDelta } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, IErrorOutput, INotebookDisplayOrder, INotebookEditData, IOrderedMimeType, IStreamOutput, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellsChangedEvent, NotebookCellsSplice2, sortMimeTypes, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, IErrorOutput, INotebookDisplayOrder, INotebookEditData, IOrderedMimeType, IStreamOutput, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellsChangedEvent, NotebookCellsSplice2, sortMimeTypes, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType, NotebookDataDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Disposable as VSCodeDisposable } from './extHostTypes'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; @@ -717,7 +717,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } this._notebookProviders.set(viewType, { extension, provider }); - this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType); + this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, false); return new VSCodeDisposable(() => { this._notebookProviders.delete(viewType); this._proxy.$unregisterNotebookProvider(viewType); @@ -735,7 +735,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } this._notebookContentProviders.set(viewType, { extension, provider }); - this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType); + this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, true); return new VSCodeDisposable(() => { this._notebookContentProviders.delete(viewType); this._proxy.$unregisterNotebookProvider(viewType); @@ -749,7 +749,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN const revivedUri = URI.revive(uri); if (!this._documents.has(revivedUri.toString())) { let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, revivedUri, this); - await this._proxy.$createNotebookDocument( + await this._proxy.$_deprecated_createNotebookDocument( document.handle, viewType, uri @@ -793,7 +793,104 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } } - async $resolveNotebook(viewType: string, uri: UriComponents): Promise { + async $resolveNotebookData(viewType: string, uri: UriComponents): Promise { + let provider = this._notebookContentProviders.get(viewType); + let document = this._documents.get(URI.revive(uri).toString()); + + if (provider && document) { + const rawCells = await provider.provider.openNotebook(URI.revive(uri)); + const renderers = new Set(); + const dto = { + metadata: { + ...notebookDocumentMetadataDefaults, + ...rawCells.metadata + }, + languages: rawCells.languages, + cells: rawCells.cells.map(cell => { + let transformedOutputs = cell.outputs.map(output => { + if (output.outputKind === CellOutputKind.Rich) { + // TODO display string[] + const ret = this._transformMimeTypes(document!, (rawCells.metadata.displayOrder as string[]) || [], output); + + if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) { + renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!); + } + return ret; + } else { + return output as IStreamOutput | IErrorOutput; + } + }); + + return { + language: cell.language, + cellKind: cell.cellKind, + metadata: cell.metadata, + source: cell.source, + outputs: transformedOutputs + }; + }) + }; + + return dto; + } + + return; + } + + private _transformMimeTypes(document: ExtHostNotebookDocument, displayOrder: string[], output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto { + let mimeTypes = Object.keys(output.data); + + // TODO@rebornix, the document display order might be assigned a bit later. We need to postpone sending the outputs to the core side. + let coreDisplayOrder = this.outputDisplayOrder; + const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], displayOrder, coreDisplayOrder?.defaultOrder || []); + + let orderMimeTypes: IOrderedMimeType[] = []; + + sorted.forEach(mimeType => { + let handlers = this.findBestMatchedRenderer(mimeType); + + if (handlers.length) { + let renderedOutput = handlers[0].render(document, output, mimeType); + + orderMimeTypes.push({ + mimeType: mimeType, + isResolved: true, + rendererId: handlers[0].handle, + output: renderedOutput + }); + + for (let i = 1; i < handlers.length; i++) { + orderMimeTypes.push({ + mimeType: mimeType, + isResolved: false, + rendererId: handlers[i].handle + }); + } + + if (mimeTypeSupportedByCore(mimeType)) { + orderMimeTypes.push({ + mimeType: mimeType, + isResolved: false, + rendererId: -1 + }); + } + } else { + orderMimeTypes.push({ + mimeType: mimeType, + isResolved: false + }); + } + }); + + return { + outputKind: output.outputKind, + data: output.data, + orderedMimeTypes: orderMimeTypes, + pickedMimeTypeIndex: 0 + }; + } + + async $_deprecated_resolveNotebook(viewType: string, uri: UriComponents): Promise { let notebookFromNotebookContentProvider = await this._resolveNotebookFromContentProvider(viewType, uri); if (notebookFromNotebookContentProvider !== undefined) { @@ -805,7 +902,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN if (provider) { if (!this._documents.has(URI.revive(uri).toString())) { let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, URI.revive(uri), this); - await this._proxy.$createNotebookDocument( + await this._proxy.$_deprecated_createNotebookDocument( document.handle, viewType, uri @@ -883,37 +980,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN return false; } - async $updateActiveEditor(viewType: string, uri: UriComponents): Promise { - this._activeNotebookDocument = this._documents.get(URI.revive(uri).toString()); - this._activeNotebookEditor = this._editors.get(URI.revive(uri).toString())?.editor; - } - - async $destoryNotebookDocument(viewType: string, uri: UriComponents): Promise { - let provider = this._notebookProviders.get(viewType); - - if (!provider) { - return false; - } - - let document = this._documents.get(URI.revive(uri).toString()); - - if (document) { - document.dispose(); - this._documents.delete(URI.revive(uri).toString()); - this._onDidCloseNotebookDocument.fire(document); - } - - let editor = this._editors.get(URI.revive(uri).toString()); - - if (editor) { - editor.editor.dispose(); - editor.onDidReceiveMessage.dispose(); - this._editors.delete(URI.revive(uri).toString()); - } - - return true; - } - $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void { this._outputDisplayOrder = displayOrder; } @@ -957,4 +1023,57 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } } } + + async $acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta) { + if (delta.removedDocuments) { + delta.removedDocuments.forEach((uri) => { + let document = this._documents.get(URI.revive(uri).toString()); + + if (document) { + document.dispose(); + this._documents.delete(URI.revive(uri).toString()); + this._onDidCloseNotebookDocument.fire(document); + } + + let editor = this._editors.get(URI.revive(uri).toString()); + + if (editor) { + editor.editor.dispose(); + editor.onDidReceiveMessage.dispose(); + this._editors.delete(URI.revive(uri).toString()); + } + }); + } + + if (delta.addedDocuments) { + delta.addedDocuments.forEach(modelData => { + const revivedUri = URI.revive(modelData.uri); + const viewType = modelData.viewType; + if (!this._documents.has(revivedUri.toString())) { + let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, revivedUri, this); + this._documents.set(revivedUri.toString(), document); + } + + const onDidReceiveMessage = new Emitter(); + + let editor = new ExtHostNotebookEditor( + viewType, + `${ExtHostNotebookController._handlePool++}`, + revivedUri, + this._proxy, + onDidReceiveMessage, + this._documents.get(revivedUri.toString())!, + this._documentsAndEditors + ); + + // TODO, does it already exist? + this._editors.set(revivedUri.toString(), { editor, onDidReceiveMessage }); + }); + } + + if (delta.newActiveEditor) { + this._activeNotebookDocument = this._documents.get(URI.revive(delta.newActiveEditor).toString()); + this._activeNotebookEditor = this._editors.get(URI.revive(delta.newActiveEditor).toString())?.editor; + } + } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index 088143de4db..c84e0cbd255 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -96,6 +96,10 @@ export class NotebookEditorInput extends EditorInput { this._onDidChangeDirty.fire(); })); + if (this.textModel.isDirty()) { + this._onDidChangeDirty.fire(); + } + return this.textModel; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 8e8a3d0c741..7c625ddac98 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -11,7 +11,7 @@ import { notebookProviderExtensionPoint, notebookRendererExtensionPoint } from ' 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 { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo, NotebookDocumentMetadata, CellEditType, ICellDto2 } 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'; @@ -202,13 +202,13 @@ export class NotebookService extends Disposable implements INotebookService, ICu return; } - async resolveNotebook(viewType: string, uri: URI): Promise { + async createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, cells: ICellDto2[]): Promise { const provider = this._notebookProviders.get(viewType); if (!provider) { return undefined; } - const notebookModel = await provider.controller.resolveNotebook(viewType, uri); + const notebookModel = await provider.controller.createNotebook(viewType, uri, true); if (!notebookModel) { return undefined; } @@ -219,6 +219,43 @@ export class NotebookService extends Disposable implements INotebookService, ICu notebookModel, (model) => this._onWillDispose(model), ); + this._models[modelId] = modelData; + + notebookModel.metadata = metadata; + notebookModel.languages = []; + + notebookModel.applyEdit(notebookModel.versionId, [ + { + editType: CellEditType.Insert, + index: 0, + cells: cells + } + ]); + + return modelData.model; + } + + async resolveNotebook(viewType: string, uri: URI): Promise { + const provider = this._notebookProviders.get(viewType); + if (!provider) { + return undefined; + } + + let notebookModel: NotebookTextModel | undefined; + + if (provider.controller.v2) { + notebookModel = await provider.controller.createNotebook(viewType, uri, false); + } else { + notebookModel = await provider.controller._deprecated_resolveNotebook(viewType, uri); + } + + // 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; } @@ -265,7 +302,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu let provider = this._notebookProviders.get(viewType); if (provider) { - provider.controller.destoryNotebookDocument(notebook); + provider.controller.removeNotebookDocument(notebook); } } diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 4c5ca6ecf6b..80eef379624 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -8,6 +8,7 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellTextModelSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IOutput, notebookDocumentMetadataDefaults, diff, ICellDeleteEdit, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ITextSnapshot } from 'vs/editor/common/model'; function compareRangesUsingEnds(a: [number, number], b: [number, number]): number { if (a[1] === b[1]) { @@ -17,6 +18,49 @@ function compareRangesUsingEnds(a: [number, number], b: [number, number]): numbe return a[1] - b[1]; } +export class NotebookTextModelSnapshot implements ITextSnapshot { + // private readonly _pieces: Ce[] = []; + private _index: number = -1; + + constructor(private _model: NotebookTextModel) { + // for (let i = 0; i < this._model.cells.length; i++) { + // const cell = this._model.cells[i]; + // this._pieces.push(this._model.cells[i].textBuffer.createSnapshot(true)); + // } + } + + read(): string | null { + + if (this._index === -1) { + this._index++; + return `{ "metadata": ${JSON.stringify(this._model.metadata)}, "cells": [`; + } + + if (this._index < this._model.cells.length) { + const cell = this._model.cells[this._index]; + + const data = { + source: cell.getValue(), + metadata: cell.metadata, + cellKind: cell.cellKind, + language: cell.language + }; + + const rawStr = JSON.stringify(data); + const isLastCell = this._index === this._model.cells.length - 1; + + this._index++; + return isLastCell ? rawStr : (rawStr + ','); + } else if (this._index === this._model.cells.length) { + this._index++; + return `]}`; + } else { + return null; + } + } + +} + export class NotebookTextModel extends Disposable implements INotebookTextModel { private static _cellhandlePool: number = 0; @@ -142,6 +186,10 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel return true; } + createSnapshot(preserveBOM?: boolean): ITextSnapshot { + return new NotebookTextModelSnapshot(this); + } + private _increaseVersionId(): void { this._versionId = this._versionId + 1; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index b2a349dd607..90006f52f2a 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -273,7 +273,7 @@ export enum CellEditType { } export interface ICellDto2 { - source: string[]; + source: string | string[]; language: string; cellKind: CellKind; outputs: IOutput[]; @@ -300,6 +300,13 @@ export interface INotebookEditData { renderers: number[]; } +export interface NotebookDataDto { + readonly cells: ICellDto2[]; + readonly languages: string[]; + readonly metadata: NotebookDocumentMetadata; +} + + export namespace CellUri { export const scheme = 'vscode-notebook'; diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index fd66b9a06b7..b6ab7c37ce2 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -15,6 +15,8 @@ import { URI } from 'vs/base/common/uri'; import { IWorkingCopyService, IWorkingCopy, IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { basename } from 'vs/base/common/resources'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { DefaultEndOfLine, ITextBuffer, EndOfLinePreference } from 'vs/editor/common/model'; export interface INotebookEditorModelManager { models: NotebookEditorModel[]; @@ -47,7 +49,8 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN public readonly resource: URI, public readonly viewType: string, @INotebookService private readonly notebookService: INotebookService, - @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, + @IBackupFileService private readonly backupFileService: IBackupFileService ) { super(); this._register(this.workingCopyService.registerWorkingCopy(this)); @@ -56,7 +59,7 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN capabilities = 0; async backup(): Promise { - return {}; + return { content: this._notebook.createSnapshot(true) }; } async revert(options?: IRevertOptions | undefined): Promise { @@ -64,20 +67,72 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN } async load(): Promise { + if (this.isResolved()) { + return this; + } + + const backup = await this.backupFileService.resolve(this.resource); + + if (this.isResolved()) { + return this; // Make sure meanwhile someone else did not succeed in loading + } + + if (backup) { + try { + return await this.loadFromBackup(backup.value.create(DefaultEndOfLine.LF)); + } catch (error) { + // this.logService.error('[text file model] load() from backup', error); // ignore error and continue to load as file below + } + } + + return this.loadFromProvider(); + } + + private async loadFromBackup(content: ITextBuffer): Promise { + const fullRange = content.getRangeAt(0, content.getLength()); + const data = JSON.parse(content.getValueInRange(fullRange, EndOfLinePreference.LF)); + + const notebook = await this.notebookService.createNotebookFromBackup(this.viewType!, this.resource, data.metadata, data.cells); + this._notebook = notebook!; + + this._name = basename(this._notebook!.uri); + + this._register(this._notebook.onDidChangeContent(() => { + this.setDirty(true); + this._onDidChangeContent.fire(); + })); + + await this.backupFileService.discardBackup(this.resource); + this.setDirty(true); + + return this; + } + + private async loadFromProvider() { const notebook = await this.notebookService.resolveNotebook(this.viewType!, this.resource); this._notebook = notebook!; this._name = basename(this._notebook!.uri); this._register(this._notebook.onDidChangeContent(() => { - this._dirty = true; - this._onDidChangeDirty.fire(); + this.setDirty(true); this._onDidChangeContent.fire(); })); return this; } + isResolved(): boolean { + return !!this._notebook; + } + + setDirty(newState: boolean) { + if (this._dirty !== newState) { + this._dirty = newState; + this._onDidChangeDirty.fire(); + } + } + isDirty() { return this._dirty; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index f846f30ffe8..767d4e617c3 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; import { Event } from 'vs/base/common/event'; -import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo, NotebookDocumentMetadata, ICellDto2 } 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'; @@ -17,11 +17,13 @@ import { INotebookEditorModelManager } from 'vs/workbench/contrib/notebook/commo export const INotebookService = createDecorator('notebookService'); export interface IMainNotebookController { - resolveNotebook(viewType: string, uri: URI): Promise; + v2: boolean; + createNotebook(viewType: string, uri: URI, fromBackup: boolean): Promise; + _deprecated_resolveNotebook(viewType: string, uri: URI): Promise; executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise; onDidReceiveMessage(uri: URI, message: any): void; executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise; - destoryNotebookDocument(notebook: INotebookTextModel): Promise; + removeNotebookDocument(notebook: INotebookTextModel): Promise; save(uri: URI, token: CancellationToken): Promise; } @@ -36,6 +38,7 @@ export interface INotebookService { unregisterNotebookRenderer(handle: number): void; getRendererInfo(handle: number): INotebookRendererInfo | undefined; resolveNotebook(viewType: string, uri: URI): Promise; + createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, cells: ICellDto2[]): Promise; executeNotebook(viewType: string, uri: URI): Promise; executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise; diff --git a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts index 0b0827adb8a..67f9b53da4e 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts @@ -41,7 +41,7 @@ suite('NotebookConcatDocument', function () { rpcProtocol.set(MainContext.MainThreadNotebook, new class extends mock() { async $registerNotebookProvider() { } async $unregisterNotebookProvider() { } - async $createNotebookDocument() { } + async $_deprecated_createNotebookDocument() { } }); extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService()); extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); @@ -49,7 +49,7 @@ suite('NotebookConcatDocument', function () { let reg = extHostNotebooks.registerNotebookProvider(nullExtensionDescription, 'test', new class extends mock() { async resolveNotebook() { } }); - await extHostNotebooks.$resolveNotebook('test', notebookUri); + await extHostNotebooks.$_deprecated_resolveNotebook('test', notebookUri); extHostNotebooks.$acceptModelChanged(notebookUri, { kind: NotebookCellsChangeType.ModelChange, versionId: 0, @@ -62,7 +62,7 @@ suite('NotebookConcatDocument', function () { outputs: [], }]]] }); - await extHostNotebooks.$updateActiveEditor('test', notebookUri); + await extHostNotebooks.$acceptDocumentAndEditorsDelta({ newActiveEditor: notebookUri }); notebook = extHostNotebooks.activeNotebookDocument!; From cb7ea6e166dcfb9cecacdd9560e8dbaa292a341f Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 5 May 2020 18:37:54 -0700 Subject: [PATCH 02/13] Remove notebook content provider --- src/vs/vscode.proposed.d.ts | 11 -- .../api/browser/mainThreadNotebook.ts | 69 ++-------- .../workbench/api/common/extHost.api.impl.ts | 4 - .../workbench/api/common/extHost.protocol.ts | 4 +- .../workbench/api/common/extHostNotebook.ts | 130 +----------------- .../notebook/browser/notebookServiceImpl.ts | 10 +- .../common/model/notebookTextModel.ts | 13 +- .../notebook/common/notebookEditorModel.ts | 2 +- .../notebook/common/notebookService.ts | 6 +- .../api/extHostNotebookConcatDocument.test.ts | 13 +- 10 files changed, 42 insertions(+), 220 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 2b42ca4451f..1f50a14b528 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1737,12 +1737,6 @@ declare module 'vscode' { edit(callback: (editBuilder: NotebookEditorCellEdit) => void): Thenable; } - export interface NotebookProvider { - resolveNotebook(editor: NotebookEditor): Promise; - executeCell(document: NotebookDocument, cell: NotebookCell | undefined, token: CancellationToken): Promise; - save(document: NotebookDocument): Promise; - } - export interface NotebookOutputSelector { type: string; subTypes?: string[]; @@ -1805,11 +1799,6 @@ declare module 'vscode' { provider: NotebookContentProvider ): Disposable; - export function registerNotebookProvider( - notebookType: string, - provider: NotebookProvider - ): Disposable; - export function registerNotebookOutputRenderer( type: string, outputSelector: NotebookOutputSelector, diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 031d901d8c5..8cafccf0955 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -8,7 +8,7 @@ import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IEx import { Disposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, CellKind, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -118,8 +118,8 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo this._notebookService.unregisterNotebookRenderer(handle); } - async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, v2: boolean): Promise { - let controller = new MainThreadNotebookController(this._proxy, this, viewType, v2); + async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise { + let controller = new MainThreadNotebookController(this._proxy, this, viewType); this._notebookProviders.set(viewType, controller); this._notebookService.registerNotebookController(viewType, extension, controller); return; @@ -131,16 +131,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo return; } - async $_deprecated_createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise { - let controller = this._notebookProviders.get(viewType); - - if (controller) { - controller._deprecated_createNotebookDocument(handle, viewType, resource); - } - - return; - } - async $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise { let controller = this._notebookProviders.get(viewType); @@ -165,11 +155,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo } } - async _deprecated_resolveNotebook(viewType: string, uri: URI): Promise { - let handle = await this._proxy.$_deprecated_resolveNotebook(viewType, uri); - return handle; - } - async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise { let controller = this._notebookProviders.get(viewType); controller?.spliceNotebookCellOutputs(resource, cellHandle, splices, renderers); @@ -202,8 +187,7 @@ export class MainThreadNotebookController implements IMainNotebookController { constructor( private readonly _proxy: ExtHostNotebookShape, private _mainThreadNotebook: MainThreadNotebooks, - private _viewType: string, - public readonly v2: boolean + private _viewType: string ) { } @@ -215,7 +199,7 @@ export class MainThreadNotebookController implements IMainNotebookController { } let document = new MainThreadNotebookDocument(this._proxy, MainThreadNotebookController.documentHandle++, viewType, uri); - await this.createNotebookDocument(document.handle, document.viewType, document.uri); + await this.createNotebookDocument(document); if (forBackup) { return document.textModel; @@ -229,39 +213,11 @@ export class MainThreadNotebookController implements IMainNotebookController { document.textModel.languages = data.languages; document.textModel.metadata = data.metadata; - document.textModel.applyEdit(document.textModel.versionId, [ - { - editType: CellEditType.Insert, - index: 0, - cells: data!.cells - } - ]); + document.textModel.initialize(data!.cells); return document.textModel; } - async _deprecated_resolveNotebook(viewType: string, uri: URI): Promise { - // TODO: resolve notebook should wait for all notebook document destory operations to finish. - let mainthreadNotebook = this._mapping.get(URI.from(uri).toString()); - - if (mainthreadNotebook) { - return mainthreadNotebook.textModel; - } - - let notebookHandle = await this._mainThreadNotebook._deprecated_resolveNotebook(viewType, uri); - if (notebookHandle !== undefined) { - mainthreadNotebook = this._mapping.get(URI.from(uri).toString()); - if (mainthreadNotebook && mainthreadNotebook.textModel.cells.length === 0) { - // it's empty, we should create an empty template one - const mainCell = mainthreadNotebook.textModel.createCellTextModel([''], mainthreadNotebook.textModel.languages.length ? mainthreadNotebook.textModel.languages[0] : '', CellKind.Code, [], undefined); - mainthreadNotebook.textModel.insertTemplateCell(mainCell); - } - return mainthreadNotebook?.textModel; - } - - return undefined; - } - async tryApplyEdits(resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise { let mainthreadNotebook = this._mapping.get(URI.from(resource).toString()); @@ -287,15 +243,14 @@ export class MainThreadNotebookController implements IMainNotebookController { this._proxy.$onDidReceiveMessage(uri, message); } - async createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise { - let document = new MainThreadNotebookDocument(this._proxy, handle, viewType, URI.revive(resource)); - this._mapping.set(URI.revive(resource).toString(), document); + async createNotebookDocument(document: MainThreadNotebookDocument): Promise { + this._mapping.set(document.uri.toString(), document); await this._proxy.$acceptDocumentAndEditorsDelta({ addedDocuments: [{ - viewType: viewType, + viewType: document.viewType, handle: document.handle, - uri: resource + uri: document.uri }] }); } @@ -313,10 +268,6 @@ export class MainThreadNotebookController implements IMainNotebookController { } // Methods for ExtHost - async _deprecated_createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise { - let document = new MainThreadNotebookDocument(this._proxy, handle, viewType, URI.revive(resource)); - this._mapping.set(URI.revive(resource).toString(), document); - } updateLanguages(resource: UriComponents, languages: string[]) { let document = this._mapping.get(URI.from(resource).toString()); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index ca1efcab306..44915fff5dd 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -910,10 +910,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostNotebook.onDidCloseNotebookDocument; }, - registerNotebookProvider: (viewType: string, provider: vscode.NotebookProvider) => { - checkProposedApiEnabled(extension); - return extHostNotebook.registerNotebookProvider(extension, viewType, provider); - }, registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider) => { checkProposedApiEnabled(extension); return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 942e16bc90c..4676efcffe5 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -687,11 +687,10 @@ export type NotebookCellOutputsSplice = [ ]; export interface MainThreadNotebookShape extends IDisposable { - $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, v2: boolean): Promise; + $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise; $unregisterNotebookProvider(viewType: string): Promise; $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise; $unregisterNotebookRenderer(handle: number): Promise; - $_deprecated_createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise; $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise; $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise; $updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise; @@ -1555,7 +1554,6 @@ export interface INotebookDocumentsAndEditorsDelta { export interface ExtHostNotebookShape { $resolveNotebookData(viewType: string, uri: UriComponents): Promise; - $_deprecated_resolveNotebook(viewType: string, uri: UriComponents): Promise; $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise; $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise; $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void; diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 2b8e61ca2c0..7a62a3e7ed0 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -625,7 +625,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private static _handlePool: number = 0; private readonly _proxy: MainThreadNotebookShape; - private readonly _notebookProviders = new Map(); private readonly _notebookContentProviders = new Map(); private readonly _documents = new Map(); private readonly _editors = new Map; }>(); @@ -706,93 +705,24 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN return matches; } - registerNotebookProvider( - extension: IExtensionDescription, - viewType: string, - provider: vscode.NotebookProvider, - ): vscode.Disposable { - - if (this._notebookProviders.has(viewType)) { - throw new Error(`Notebook provider for '${viewType}' already registered`); - } - - this._notebookProviders.set(viewType, { extension, provider }); - this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, false); - return new VSCodeDisposable(() => { - this._notebookProviders.delete(viewType); - this._proxy.$unregisterNotebookProvider(viewType); - }); - } - registerNotebookContentProvider( extension: IExtensionDescription, viewType: string, provider: vscode.NotebookContentProvider, ): vscode.Disposable { - if (this._notebookProviders.has(viewType)) { + if (this._notebookContentProviders.has(viewType)) { throw new Error(`Notebook provider for '${viewType}' already registered`); } this._notebookContentProviders.set(viewType, { extension, provider }); - this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, true); + this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType); return new VSCodeDisposable(() => { this._notebookContentProviders.delete(viewType); this._proxy.$unregisterNotebookProvider(viewType); }); } - async _resolveNotebookFromContentProvider(viewType: string, uri: UriComponents): Promise { - let provider = this._notebookContentProviders.get(viewType); - - if (provider) { - const revivedUri = URI.revive(uri); - if (!this._documents.has(revivedUri.toString())) { - let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, revivedUri, this); - await this._proxy.$_deprecated_createNotebookDocument( - document.handle, - viewType, - uri - ); - - this._documents.set(revivedUri.toString(), document); - } - - const onDidReceiveMessage = new Emitter(); - - let editor = new ExtHostNotebookEditor( - viewType, - `${ExtHostNotebookController._handlePool++}`, - revivedUri, - this._proxy, - onDidReceiveMessage, - this._documents.get(revivedUri.toString())!, - this._documentsAndEditors - ); - - this._editors.set(revivedUri.toString(), { editor, onDidReceiveMessage }); - - const data = await provider.provider.openNotebook(revivedUri); - editor.document.languages = data.languages; - editor.document.metadata = { - ...notebookDocumentMetadataDefaults, - ...data.metadata - }; - - await editor.edit(editBuilder => { - for (let i = 0; i < data.cells.length; i++) { - const cell = data.cells[i]; - editBuilder.insert(0, cell.source, cell.language, cell.cellKind, cell.outputs, cell.metadata); - } - }); - - this._onDidOpenNotebookDocument.fire(editor.document); - return editor.document.handle; - } else { - return Promise.resolve(undefined); - } - } - async $resolveNotebookData(viewType: string, uri: UriComponents): Promise { let provider = this._notebookContentProviders.get(viewType); let document = this._documents.get(URI.revive(uri).toString()); @@ -890,48 +820,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN }; } - async $_deprecated_resolveNotebook(viewType: string, uri: UriComponents): Promise { - let notebookFromNotebookContentProvider = await this._resolveNotebookFromContentProvider(viewType, uri); - - if (notebookFromNotebookContentProvider !== undefined) { - return notebookFromNotebookContentProvider; - } - - let provider = this._notebookProviders.get(viewType); - - if (provider) { - if (!this._documents.has(URI.revive(uri).toString())) { - let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, URI.revive(uri), this); - await this._proxy.$_deprecated_createNotebookDocument( - document.handle, - viewType, - uri - ); - - this._documents.set(URI.revive(uri).toString(), document); - } - - const onDidReceiveMessage = new Emitter(); - - let editor = new ExtHostNotebookEditor( - viewType, - `${ExtHostNotebookController._handlePool++}`, - URI.revive(uri), - this._proxy, - onDidReceiveMessage, - this._documents.get(URI.revive(uri).toString())!, - this._documentsAndEditors - ); - - this._editors.set(URI.revive(uri).toString(), { editor, onDidReceiveMessage }); - await provider.provider.resolveNotebook(editor); - // await editor.document.$updateCells(); - return editor.document.handle; - } - - return Promise.resolve(undefined); - } - async $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise { let document = this._documents.get(URI.revive(uri).toString()); @@ -944,15 +832,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN return this._notebookContentProviders.get(viewType)!.provider.executeCell(document, cell, token); } - - let provider = this._notebookProviders.get(viewType); - - if (!provider) { - return; - } - - let cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined; - return provider.provider.executeCell(document!, cell, token); } async $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise { @@ -971,10 +850,11 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN return true; } - let provider = this._notebookProviders.get(viewType); + let provider = this._notebookContentProviders.get(viewType); if (provider && document) { - return await provider.provider.save(document); + await provider.provider.saveNotebook(document, token); + return true; } return false; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 7c625ddac98..30dbe298b03 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -202,7 +202,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu return; } - async createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, cells: ICellDto2[]): Promise { + async createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[]): Promise { const provider = this._notebookProviders.get(viewType); if (!provider) { return undefined; @@ -222,7 +222,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu this._models[modelId] = modelData; notebookModel.metadata = metadata; - notebookModel.languages = []; + notebookModel.languages = languages; notebookModel.applyEdit(notebookModel.versionId, [ { @@ -243,11 +243,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu let notebookModel: NotebookTextModel | undefined; - if (provider.controller.v2) { - notebookModel = await provider.controller.createNotebook(viewType, uri, false); - } else { - notebookModel = await provider.controller._deprecated_resolveNotebook(viewType, uri); - } + notebookModel = await provider.controller.createNotebook(viewType, uri, false); // new notebook model created const modelId = MODEL_ID(uri); diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 80eef379624..fce799cc033 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -7,7 +7,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellTextModelSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IOutput, notebookDocumentMetadataDefaults, diff, ICellDeleteEdit, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellTextModelSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IOutput, notebookDocumentMetadataDefaults, diff, ICellDeleteEdit, NotebookCellsChangeType, ICellDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ITextSnapshot } from 'vs/editor/common/model'; function compareRangesUsingEnds(a: [number, number], b: [number, number]): number { @@ -33,7 +33,7 @@ export class NotebookTextModelSnapshot implements ITextSnapshot { if (this._index === -1) { this._index++; - return `{ "metadata": ${JSON.stringify(this._model.metadata)}, "cells": [`; + return `{ "metadata": ${JSON.stringify(this._model.metadata)}, "languages": ${JSON.stringify(this._model.languages)}, "cells": [`; } if (this._index < this._model.cells.length) { @@ -121,6 +121,15 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel return new NotebookCellTextModel(cellUri, cellHandle, source, language, cellKind, outputs || [], metadata); } + initialize(cells: ICellDto2[]) { + const mainCells = cells.map(cell => { + const cellHandle = NotebookTextModel._cellhandlePool++; + const cellUri = CellUri.generate(this.uri, cellHandle); + return new NotebookCellTextModel(cellUri, cellHandle, cell.source, cell.language, cell.cellKind, cell.outputs || [], cell.metadata); + }); + this.insertNewCell(0, mainCells); + } + applyEdit(modelVersionId: number, rawEdits: ICellEditOperation[]): boolean { if (modelVersionId !== this._versionId) { return false; diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index b6ab7c37ce2..1ab0ecb3ea5 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -92,7 +92,7 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN const fullRange = content.getRangeAt(0, content.getLength()); const data = JSON.parse(content.getValueInRange(fullRange, EndOfLinePreference.LF)); - const notebook = await this.notebookService.createNotebookFromBackup(this.viewType!, this.resource, data.metadata, data.cells); + const notebook = await this.notebookService.createNotebookFromBackup(this.viewType!, this.resource, data.metadata, data.languages, data.cells); this._notebook = notebook!; this._name = basename(this._notebook!.uri); diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index 767d4e617c3..2d92cd67e51 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -17,9 +17,7 @@ import { INotebookEditorModelManager } from 'vs/workbench/contrib/notebook/commo export const INotebookService = createDecorator('notebookService'); export interface IMainNotebookController { - v2: boolean; - createNotebook(viewType: string, uri: URI, fromBackup: boolean): Promise; - _deprecated_resolveNotebook(viewType: string, uri: URI): Promise; + createNotebook(viewType: string, uri: URI, forBackup: boolean): Promise; executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise; onDidReceiveMessage(uri: URI, message: any): void; executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise; @@ -38,7 +36,7 @@ export interface INotebookService { unregisterNotebookRenderer(handle: number): void; getRendererInfo(handle: number): INotebookRendererInfo | undefined; resolveNotebook(viewType: string, uri: URI): Promise; - createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, cells: ICellDto2[]): Promise; + createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[]): Promise; executeNotebook(viewType: string, uri: URI): Promise; executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise; diff --git a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts index 67f9b53da4e..7695ee2df5b 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts @@ -41,15 +41,20 @@ suite('NotebookConcatDocument', function () { rpcProtocol.set(MainContext.MainThreadNotebook, new class extends mock() { async $registerNotebookProvider() { } async $unregisterNotebookProvider() { } - async $_deprecated_createNotebookDocument() { } }); extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService()); extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors); - let reg = extHostNotebooks.registerNotebookProvider(nullExtensionDescription, 'test', new class extends mock() { - async resolveNotebook() { } + let reg = extHostNotebooks.registerNotebookContentProvider(nullExtensionDescription, 'test', new class extends mock() { + // async openNotebook() { } + }); + await extHostNotebooks.$acceptDocumentAndEditorsDelta({ + addedDocuments: [{ + handle: 0, + uri: notebookUri, + viewType: 'test' + }] }); - await extHostNotebooks.$_deprecated_resolveNotebook('test', notebookUri); extHostNotebooks.$acceptModelChanged(notebookUri, { kind: NotebookCellsChangeType.ModelChange, versionId: 0, From 3c710dfcc996957aa3e12bccc174ce7af1df03fa Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 5 May 2020 18:47:18 -0700 Subject: [PATCH 03/13] Transform outputs is now safe --- src/vs/workbench/api/common/extHostNotebook.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 7a62a3e7ed0..683823daa93 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -345,8 +345,6 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo transformMimeTypes(output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto { let mimeTypes = Object.keys(output.data); - - // TODO@rebornix, the document display order might be assigned a bit later. We need to postpone sending the outputs to the core side. let coreDisplayOrder = this.renderingHandler.outputDisplayOrder; const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], this._displayOrder, coreDisplayOrder?.defaultOrder || []); @@ -415,7 +413,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo } } -export class NotebookEditorCellEdit { +export class NotebookEditorCellEditBuilder implements vscode.NotebookEditorCellEdit { private _finalized: boolean = false; private readonly _documentVersionId: number; private _collectedEdits: ICellEditOperation[] = []; @@ -526,13 +524,13 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook })); } - edit(callback: (editBuilder: NotebookEditorCellEdit) => void): Thenable { - const edit = new NotebookEditorCellEdit(this); + edit(callback: (editBuilder: NotebookEditorCellEditBuilder) => void): Thenable { + const edit = new NotebookEditorCellEditBuilder(this); callback(edit); return this._applyEdit(edit); } - private _applyEdit(editBuilder: NotebookEditorCellEdit): Promise { + private _applyEdit(editBuilder: NotebookEditorCellEditBuilder): Promise { const editData = editBuilder.finalize(); // return when there is nothing to do @@ -769,8 +767,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private _transformMimeTypes(document: ExtHostNotebookDocument, displayOrder: string[], output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto { let mimeTypes = Object.keys(output.data); - - // TODO@rebornix, the document display order might be assigned a bit later. We need to postpone sending the outputs to the core side. let coreDisplayOrder = this.outputDisplayOrder; const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], displayOrder, coreDisplayOrder?.defaultOrder || []); From a9da51fd579a5a42d859dc44ee02c7b42c6e21d2 Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 5 May 2020 18:50:00 -0700 Subject: [PATCH 04/13] document add/delete events --- src/vs/workbench/api/common/extHostNotebook.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 683823daa93..a9ce914cdfe 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -931,6 +931,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } const onDidReceiveMessage = new Emitter(); + const document = this._documents.get(revivedUri.toString())!; let editor = new ExtHostNotebookEditor( viewType, @@ -938,10 +939,12 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN revivedUri, this._proxy, onDidReceiveMessage, - this._documents.get(revivedUri.toString())!, + document, this._documentsAndEditors ); + this._onDidOpenNotebookDocument.fire(document); + // TODO, does it already exist? this._editors.set(revivedUri.toString(), { editor, onDidReceiveMessage }); }); From b4dd66bc856e0ad1231c5c19e0465412aace0af6 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 6 May 2020 11:43:19 -0700 Subject: [PATCH 05/13] revert. --- .../api/browser/mainThreadNotebook.ts | 17 +++++++++-- .../notebook/browser/notebookEditorInput.ts | 12 ++++++++ .../notebook/browser/notebookServiceImpl.ts | 6 ++-- .../browser/viewModel/notebookViewModel.ts | 4 +-- .../common/model/notebookTextModel.ts | 19 +++++++----- .../notebook/common/notebookEditorModel.ts | 30 ++++++++++++++++--- .../notebook/common/notebookService.ts | 4 +-- 7 files changed, 72 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 8cafccf0955..a9852215006 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -8,7 +8,7 @@ import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IEx import { Disposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -191,10 +191,23 @@ export class MainThreadNotebookController implements IMainNotebookController { ) { } - async createNotebook(viewType: string, uri: URI, forBackup: boolean): Promise { + async createNotebook(viewType: string, uri: URI, forBackup: boolean, forceReload: boolean): Promise { let mainthreadNotebook = this._mapping.get(URI.from(uri).toString()); if (mainthreadNotebook) { + if (forceReload) { + const data = await this._proxy.$resolveNotebookData(viewType, uri); + if (!data) { + return; + } + + mainthreadNotebook.textModel.languages = data.languages; + mainthreadNotebook.textModel.metadata = data.metadata; + mainthreadNotebook.textModel.applyEdit(mainthreadNotebook.textModel.versionId, [ + { editType: CellEditType.Delete, count: mainthreadNotebook.textModel.cells.length, index: 0 }, + { editType: CellEditType.Insert, index: 0, cells: data.cells } + ]); + } return mainthreadNotebook.textModel; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index c84e0cbd255..d0f815bdbf9 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -85,6 +85,18 @@ export class NotebookEditorInput extends EditorInput { return undefined; } + async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise { + return this; + } + + async revert(group: GroupIdentifier): Promise { + if (this.textModel) { + await this.textModel.revert(); + } + + return; + } + async resolve(): Promise { if (!await this.notebookService.canResolve(this.viewType!)) { throw new Error(`Cannot open notebook of type '${this.viewType}'`); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 30dbe298b03..0f4affb9d95 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -208,7 +208,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu return undefined; } - const notebookModel = await provider.controller.createNotebook(viewType, uri, true); + const notebookModel = await provider.controller.createNotebook(viewType, uri, true, false); if (!notebookModel) { return undefined; } @@ -235,7 +235,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu return modelData.model; } - async resolveNotebook(viewType: string, uri: URI): Promise { + async resolveNotebook(viewType: string, uri: URI, forceReload: boolean): Promise { const provider = this._notebookProviders.get(viewType); if (!provider) { return undefined; @@ -243,7 +243,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu let notebookModel: NotebookTextModel | undefined; - notebookModel = await provider.controller.createNotebook(viewType, uri, false); + notebookModel = await provider.controller.createNotebook(viewType, uri, false, forceReload); // new notebook model created const modelId = MODEL_ID(uri); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index e8016684b27..845b57e6e16 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -586,7 +586,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this._viewCells.splice(deleteIndex, 1); this._handleToViewCellMapping.delete(deleteCell.handle); - this._notebook.removeCell(deleteIndex); + this._notebook.removeCell(deleteIndex, 1); this._onDidChangeViewCells.fire({ synchronous: true, splices: [[deleteIndex, 1, []]] }); } @@ -638,7 +638,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this._viewCells.splice(index, 1); this._handleToViewCellMapping.delete(viewCell.handle); - this._notebook.removeCell(index); + this._notebook.removeCell(index, 1); let endSelections: number[] = []; if (this.selectionHandles.length) { diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index fce799cc033..f768bc5d1ce 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -122,6 +122,9 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel } initialize(cells: ICellDto2[]) { + this.cells = []; + this._versionId = 0; + const mainCells = cells.map(cell => { const cellHandle = NotebookTextModel._cellhandlePool++; const cellUri = CellUri.generate(this.uri, cellHandle); @@ -180,7 +183,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel this.insertNewCell(insertEdit.index, mainCells); break; case CellEditType.Delete: - this.removeCell(operations[i].index); + this.removeCell(operations[i].index, operations[i].end - operations[i].start); break; } } @@ -307,17 +310,19 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel return; } - removeCell(index: number) { + removeCell(index: number, count: number) { this._isUntitled = false; - let cell = this.cells[index]; - this._cellListeners.get(cell.handle)?.dispose(); - this._cellListeners.delete(cell.handle); - this.cells.splice(index, 1); + for (let i = index; i < index + count; i++) { + let cell = this.cells[i]; + this._cellListeners.get(cell.handle)?.dispose(); + this._cellListeners.delete(cell.handle); + } + this.cells.splice(index, count); this._onDidChangeContent.fire(); this._increaseVersionId(); - this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.ModelChange, versionId: this._versionId, changes: [[index, 1, []]] }); + this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.ModelChange, versionId: this._versionId, changes: [[index, count, []]] }); } moveCellToIdx(index: number, newIdx: number) { diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 1ab0ecb3ea5..784c1cdebf9 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -26,6 +26,13 @@ export interface INotebookEditorModelManager { get(resource: URI): NotebookEditorModel | undefined; } +export interface INotebookRevertOptions { + /** + * Go to disk bypassing any cache of the model if any. + */ + forceReadFromDisk?: boolean; +} + export class NotebookEditorModel extends EditorModel implements IWorkingCopy, INotebookEditorModel { private _dirty = false; @@ -63,10 +70,17 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN } async revert(options?: IRevertOptions | undefined): Promise { + console.log(options); + await this.load({ forceReadFromDisk: true }); + this._dirty = false; + this._onDidChangeDirty.fire(); return; } - async load(): Promise { + async load(options?: INotebookRevertOptions): Promise { + if (options?.forceReadFromDisk) { + return this.loadFromProvider(true); + } if (this.isResolved()) { return this; } @@ -85,7 +99,7 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN } } - return this.loadFromProvider(); + return this.loadFromProvider(false); } private async loadFromBackup(content: ITextBuffer): Promise { @@ -108,8 +122,8 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN return this; } - private async loadFromProvider() { - const notebook = await this.notebookService.resolveNotebook(this.viewType!, this.resource); + private async loadFromProvider(forceReloadFromDisk: boolean) { + const notebook = await this.notebookService.resolveNotebook(this.viewType!, this.resource, forceReloadFromDisk); this._notebook = notebook!; this._name = basename(this._notebook!.uri); @@ -144,6 +158,14 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN this._onDidChangeDirty.fire(); return true; } + + async saveAs(targetResource: URI): Promise { + const tokenSource = new CancellationTokenSource(); + await this.notebookService.save(this.notebook.viewType, this.notebook.uri, tokenSource.token); + this._dirty = false; + this._onDidChangeDirty.fire(); + return true; + } } export class NotebookEditorModelManager extends Disposable implements INotebookEditorModelManager { diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index 2d92cd67e51..e737cbfb348 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -17,7 +17,7 @@ import { INotebookEditorModelManager } from 'vs/workbench/contrib/notebook/commo export const INotebookService = createDecorator('notebookService'); export interface IMainNotebookController { - createNotebook(viewType: string, uri: URI, forBackup: boolean): Promise; + createNotebook(viewType: string, uri: URI, forBackup: boolean, forceReload: boolean): Promise; executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise; onDidReceiveMessage(uri: URI, message: any): void; executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise; @@ -35,7 +35,7 @@ export interface INotebookService { registerNotebookRenderer(handle: number, extensionData: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: URI[]): void; unregisterNotebookRenderer(handle: number): void; getRendererInfo(handle: number): INotebookRendererInfo | undefined; - resolveNotebook(viewType: string, uri: URI): Promise; + resolveNotebook(viewType: string, uri: URI, forceReload: boolean): Promise; createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[]): Promise; executeNotebook(viewType: string, uri: URI): Promise; executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise; From 4447915b001ba88c53bc94ddc71b5084e470e1bc Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 6 May 2020 12:42:36 -0700 Subject: [PATCH 06/13] saveAs --- .../api/browser/mainThreadNotebook.ts | 5 ++++ .../workbench/api/common/extHost.protocol.ts | 1 + .../workbench/api/common/extHostNotebook.ts | 18 +++++++++-- .../notebook/browser/notebookEditorInput.ts | 30 +++++++++++++++++-- .../notebook/browser/notebookServiceImpl.ts | 10 +++++++ .../notebook/common/notebookEditorModel.ts | 2 +- .../notebook/common/notebookService.ts | 2 ++ 7 files changed, 61 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index a9852215006..efdef129e3f 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -309,4 +309,9 @@ export class MainThreadNotebookController implements IMainNotebookController { async save(uri: URI, token: CancellationToken): Promise { return this._proxy.$saveNotebook(this._viewType, uri, token); } + + async saveAs(uri: URI, target: URI, token: CancellationToken): Promise { + return this._proxy.$saveNotebookAs(this._viewType, uri, target, token); + + } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 4676efcffe5..9340648be72 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1556,6 +1556,7 @@ export interface ExtHostNotebookShape { $resolveNotebookData(viewType: string, uri: UriComponents): Promise; $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise; $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise; + $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise; $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void; $onDidReceiveMessage(uri: UriComponents, message: any): void; $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void; diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index a9ce914cdfe..66dba087614 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -846,10 +846,22 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN return true; } - let provider = this._notebookContentProviders.get(viewType); + return false; + } + + async $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise { + let document = this._documents.get(URI.revive(uri).toString()); + if (!document) { + return false; + } + + if (this._notebookContentProviders.has(viewType)) { + try { + await this._notebookContentProviders.get(viewType)!.provider.saveNotebookAs(URI.revive(target), document, token); + } catch (e) { + return false; + } - if (provider && document) { - await provider.provider.saveNotebook(document, token); return true; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index d0f815bdbf9..72cdd118a4c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -6,10 +6,12 @@ import { EditorInput, IEditorInput, GroupIdentifier, ISaveOptions } from 'vs/workbench/common/editor'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { URI } from 'vs/base/common/uri'; -import { isEqual } from 'vs/base/common/resources'; +import { isEqual, basename } from 'vs/base/common/resources'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; +import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class NotebookEditorInput extends EditorInput { @@ -39,7 +41,10 @@ export class NotebookEditorInput extends EditorInput { public name: string, public readonly viewType: string | undefined, @INotebookService private readonly notebookService: INotebookService, - @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @IEditorService private readonly editorService: IEditorService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); } @@ -86,7 +91,26 @@ export class NotebookEditorInput extends EditorInput { } async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise { - return this; + if (!this.textModel) { + return undefined; + } + + const dialogPath = this.textModel.resource; + const target = await this.fileDialogService.pickFileToSave(dialogPath, options?.availableFileSystems); + if (!target) { + return undefined; // save cancelled + } + + if (!await this.textModel.saveAs(target)) { + return undefined; + } + + return this._move(group, target)?.editor; + } + + _move(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined { + const editorInput = NotebookEditorInput.getOrCreate(this.instantiationService, newResource, basename(newResource), this.viewType); + return { editor: editorInput }; } async revert(group: GroupIdentifier): Promise { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 0f4affb9d95..ca530378bbc 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -324,6 +324,16 @@ export class NotebookService extends Disposable implements INotebookService, ICu return false; } + async saveAs(viewType: string, resource: URI, target: URI, token: CancellationToken): Promise { + let provider = this._notebookProviders.get(viewType); + + if (provider) { + return provider.controller.saveAs(resource, target, token); + } + + return false; + } + onDidReceiveMessage(viewType: string, uri: URI, message: any): void { let provider = this._notebookProviders.get(viewType); diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 784c1cdebf9..3660ea57838 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -161,7 +161,7 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN async saveAs(targetResource: URI): Promise { const tokenSource = new CancellationTokenSource(); - await this.notebookService.save(this.notebook.viewType, this.notebook.uri, tokenSource.token); + await this.notebookService.saveAs(this.notebook.viewType, this.notebook.uri, targetResource, tokenSource.token); this._dirty = false; this._onDidChangeDirty.fire(); return true; diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index e737cbfb348..6a808bafa99 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -23,6 +23,7 @@ export interface IMainNotebookController { executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise; removeNotebookDocument(notebook: INotebookTextModel): Promise; save(uri: URI, token: CancellationToken): Promise; + saveAs(uri: URI, target: URI, token: CancellationToken): Promise; } export interface INotebookService { @@ -46,6 +47,7 @@ export interface INotebookService { destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void; updateActiveNotebookDocument(viewType: string, resource: URI): void; save(viewType: string, resource: URI, token: CancellationToken): Promise; + saveAs(viewType: string, resource: URI, target: URI, token: CancellationToken): Promise; onDidReceiveMessage(viewType: string, uri: URI, message: any): void; setToCopy(items: NotebookCellTextModel[]): void; getToCopy(): NotebookCellTextModel[] | undefined; From e9a1035f5a5254fb09114c0026c12631a9b35cb0 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 6 May 2020 13:41:04 -0700 Subject: [PATCH 07/13] :lipstick: --- .../workbench/contrib/notebook/browser/notebookEditorInput.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index 72cdd118a4c..50405b6e34c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -11,7 +11,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class NotebookEditorInput extends EditorInput { @@ -43,7 +42,7 @@ export class NotebookEditorInput extends EditorInput { @INotebookService private readonly notebookService: INotebookService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @IFileDialogService private readonly fileDialogService: IFileDialogService, - @IEditorService private readonly editorService: IEditorService, + // @IEditorService private readonly editorService: IEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); From 4bd68b7dfe69c72c09d05a880e236d707c9f8b98 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 6 May 2020 15:04:52 -0700 Subject: [PATCH 08/13] fix false negative test --- extensions/vscode-notebook-tests/src/notebook.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vscode-notebook-tests/src/notebook.test.ts b/extensions/vscode-notebook-tests/src/notebook.test.ts index e4eba9aefb3..2a85f394229 100644 --- a/extensions/vscode-notebook-tests/src/notebook.test.ts +++ b/extensions/vscode-notebook-tests/src/notebook.test.ts @@ -212,7 +212,7 @@ suite('notebook dirty state', () => { await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' }); - // await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.files.newUntitledFile'); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); From e0882b95ff62fb2562264218ac202ccb12bd4753 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 6 May 2020 16:42:38 -0700 Subject: [PATCH 09/13] Fix selection after reverting the document --- .../src/notebook.test.ts | 48 +++++++++++++++++++ .../notebook/browser/view/notebookCellList.ts | 18 +++++-- .../browser/viewModel/notebookViewModel.ts | 11 +++-- .../notebook/common/notebookEditorModel.ts | 1 - 4 files changed, 71 insertions(+), 7 deletions(-) diff --git a/extensions/vscode-notebook-tests/src/notebook.test.ts b/extensions/vscode-notebook-tests/src/notebook.test.ts index 2a85f394229..0de2917cf3f 100644 --- a/extensions/vscode-notebook-tests/src/notebook.test.ts +++ b/extensions/vscode-notebook-tests/src/notebook.test.ts @@ -272,3 +272,51 @@ suite('notebook undo redo', () => { await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); }); }); + +suite('notebook working copy', () => { + test('notebook revert on close', async function () { + const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + await waitFor(500); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, ''); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' }); + + // close active editor from command will revert the file + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + await waitFor(500); + assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true); + assert.equal(vscode.notebook.activeNotebookEditor?.selection !== undefined, true); + assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells[0], vscode.notebook.activeNotebookEditor?.selection); + assert.equal(vscode.notebook.activeNotebookEditor?.selection?.source, 'test'); + + await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + }); + + test('notebook revert', async function () { + const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + await waitFor(500); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, ''); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' }); + await vscode.commands.executeCommand('workbench.action.files.revert'); + + assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true); + assert.equal(vscode.notebook.activeNotebookEditor?.selection !== undefined, true); + assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells[0], vscode.notebook.activeNotebookEditor?.selection); + assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells.length, 1); + assert.equal(vscode.notebook.activeNotebookEditor?.selection?.source, 'test'); + + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + }); +}); diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 5f7766fd2e4..f82bf348e93 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -157,7 +157,7 @@ export class NotebookCellList extends WorkbenchList implements ID for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { const cell = this.element(i); - if (this._viewModel!.hasCell(cell)) { + if (this._viewModel!.hasCell(cell.handle)) { hideOutputs.push(...cell?.model.outputs); } else { deletedOutputs.push(...cell?.model.outputs); @@ -177,7 +177,7 @@ export class NotebookCellList extends WorkbenchList implements ID for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { const cell = this.element(i); - if (this._viewModel!.hasCell(cell)) { + if (this._viewModel!.hasCell(cell.handle)) { hideOutputs.push(...cell?.model.outputs); } else { deletedOutputs.push(...cell?.model.outputs); @@ -299,7 +299,7 @@ export class NotebookCellList extends WorkbenchList implements ID for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { const cell = this.element(i); - if (this._viewModel!.hasCell(cell)) { + if (this._viewModel!.hasCell(cell.handle)) { hideOutputs.push(...cell?.model.outputs); } else { deletedOutputs.push(...cell?.model.outputs); @@ -316,6 +316,18 @@ export class NotebookCellList extends WorkbenchList implements ID splice2(start: number, deleteCount: number, elements: CellViewModel[] = []): void { // we need to convert start and delete count based on hidden ranges super.splice(start, deleteCount, elements); + + const selectionsLeft = []; + this._viewModel!.selectionHandles.forEach(handle => { + if (this._viewModel!.hasCell(handle)) { + selectionsLeft.push(handle); + } + }); + + if (!selectionsLeft.length && this._viewModel!.viewCells) { + // after splice, the selected cells are deleted + this._viewModel!.selectionHandles = [this._viewModel!.viewCells[0].handle]; + } } getViewIndex(cell: ICellViewModel) { diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index 845b57e6e16..5b56bce0939 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -268,7 +268,12 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD }); diffs.reverse().forEach(diff => { - this._viewCells.splice(diff[0], diff[1], ...diff[2]); + const deletedCells = this._viewCells.splice(diff[0], diff[1], ...diff[2]); + + deletedCells.forEach(cell => { + this._handleToViewCellMapping.delete(cell.handle); + }); + diff[2].forEach(cell => { this._handleToViewCellMapping.set(cell.handle, cell); this._localStore.add(cell); @@ -456,8 +461,8 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD return index + 1; } - hasCell(cell: ICellViewModel) { - return this._handleToViewCellMapping.has(cell.handle); + hasCell(handle: number) { + return this._handleToViewCellMapping.has(handle); } getVersionId() { diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 3660ea57838..bad8f0edc22 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -70,7 +70,6 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN } async revert(options?: IRevertOptions | undefined): Promise { - console.log(options); await this.load({ forceReadFromDisk: true }); this._dirty = false; this._onDidChangeDirty.fire(); From 9836cd2f6850610e4c9c56493a1f9af89c820408 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 6 May 2020 16:53:28 -0700 Subject: [PATCH 10/13] Fix #97108 --- src/vs/workbench/contrib/notebook/browser/media/notebook.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index 1a7895cefd8..e17833c1ff5 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -21,6 +21,10 @@ overflow: visible !important; } */ +.monaco-workbench .part.editor > .content .notebook-editor .simple-fr-find-part-wrapper.visible { + z-index: 100; +} + .monaco-workbench .part.editor > .content .notebook-editor .cell-list-container .overflowingContentWidgets > div { z-index: 600 !important; /* @rebornix: larger than the editor title bar */ From 28b887934d895847c88677388fa891b2e58a7935 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 6 May 2020 17:30:58 -0700 Subject: [PATCH 11/13] Fix #96685 --- .../notebook/browser/notebookEditorInput.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index 50405b6e34c..b392a0fab2d 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { EditorInput, IEditorInput, GroupIdentifier, ISaveOptions } from 'vs/workbench/common/editor'; +import { EditorInput, IEditorInput, GroupIdentifier, ISaveOptions, IMoveResult } from 'vs/workbench/common/editor'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { URI } from 'vs/base/common/uri'; -import { isEqual, basename } from 'vs/base/common/resources'; +import { isEqual, basename, extname } from 'vs/base/common/resources'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; @@ -107,6 +107,17 @@ export class NotebookEditorInput extends EditorInput { return this._move(group, target)?.editor; } + move(group: GroupIdentifier, target: URI): IMoveResult | undefined { + if (this.textModel) { + const contributedNotebookProviders = this.notebookService.getContributedNotebookProviders(target); + + if (contributedNotebookProviders.find(provider => provider.id === this.textModel!.viewType)) { + return this._move(group, target); + } + } + return undefined; + } + _move(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined { const editorInput = NotebookEditorInput.getOrCreate(this.instantiationService, newResource, basename(newResource), this.viewType); return { editor: editorInput }; From b38ccc0084ce6f6ad289e3a8866db023d38001d8 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 6 May 2020 17:48:21 -0700 Subject: [PATCH 12/13] remove unused. --- .../workbench/contrib/notebook/browser/notebookEditorInput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index b392a0fab2d..f923d14f0b1 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -6,7 +6,7 @@ import { EditorInput, IEditorInput, GroupIdentifier, ISaveOptions, IMoveResult } from 'vs/workbench/common/editor'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { URI } from 'vs/base/common/uri'; -import { isEqual, basename, extname } from 'vs/base/common/resources'; +import { isEqual, basename } from 'vs/base/common/resources'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; From ddbaab0fbc1d0c48a2ef7e855a56d21b2f63dc66 Mon Sep 17 00:00:00 2001 From: rebornix Date: Thu, 7 May 2020 09:38:09 -0700 Subject: [PATCH 13/13] Re #96564 --- .../contrib/notebook/browser/notebookEditorInput.ts | 6 +++--- .../contrib/notebook/common/notebookEditorModel.ts | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index f923d14f0b1..fd7dff392af 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { EditorInput, IEditorInput, GroupIdentifier, ISaveOptions, IMoveResult } from 'vs/workbench/common/editor'; +import { EditorInput, IEditorInput, GroupIdentifier, ISaveOptions, IMoveResult, IRevertOptions } from 'vs/workbench/common/editor'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { URI } from 'vs/base/common/uri'; import { isEqual, basename } from 'vs/base/common/resources'; @@ -123,9 +123,9 @@ export class NotebookEditorInput extends EditorInput { return { editor: editorInput }; } - async revert(group: GroupIdentifier): Promise { + async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { if (this.textModel) { - await this.textModel.revert(); + await this.textModel.revert(options); } return; diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index bad8f0edc22..823c790efbb 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -70,6 +70,11 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN } async revert(options?: IRevertOptions | undefined): Promise { + if (options?.soft) { + await this.backupFileService.discardBackup(this.resource); + return; + } + await this.load({ forceReadFromDisk: true }); this._dirty = false; this._onDidChangeDirty.fire();