diff --git a/extensions/vscode-notebook-tests/src/notebook.test.ts b/extensions/vscode-notebook-tests/src/notebook.test.ts index 2c5aef5479a6b056d69124ec6eb0eef4cdb1c51d..f08c9e2d4ae828ee3c53181a3db080c5424797fd 100644 --- a/extensions/vscode-notebook-tests/src/notebook.test.ts +++ b/extensions/vscode-notebook-tests/src/notebook.test.ts @@ -112,6 +112,7 @@ suite('notebook workflow', () => { // ---- ---- // + await vscode.commands.executeCommand('workbench.action.files.save'); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); }); @@ -161,6 +162,7 @@ suite('notebook workflow', () => { await vscode.commands.executeCommand('notebook.cell.execute'); assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked + await vscode.commands.executeCommand('workbench.action.files.save'); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); }); @@ -183,6 +185,7 @@ suite('notebook workflow', () => { await vscode.commands.executeCommand('notebook.execute'); assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked + await vscode.commands.executeCommand('workbench.action.files.save'); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); }); }); diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 52b353c6358d1a1b5f0a3d69fa7a8530f9291a5c..e6b470a236c1564b7fcf1151fd112153f387c57a 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -7,7 +7,7 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext } from '../common/extHost.protocol'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/browser/notebookService'; +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 { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index ac04132c2e17a91d511bdecf238dd0109b4df081..8cb8adf0423b7f811b9b879bb3370a7d70ee6505 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -16,7 +16,7 @@ import { BaseCellRenderTemplate, CellEditState, CellRunState, ICellViewModel, IN import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index f13a3d5f30f6e82d492e6d82994b1081b03c2bf9..a726ac0d66a0062b7fe226918d8c7f207edc5540 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -26,7 +26,8 @@ import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchCo import { EditorInput, Extensions as EditorInputExtensions, IEditorInput, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { NotebookEditor, NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; -import { INotebookService, NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookServiceImpl'; import { CellKind, CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -247,11 +248,13 @@ class CellContentProvider implements ITextModelContentProvider { if (!info) { return null; } - const notebook = await this._notebookService.resolveNotebook(info.id, data.notebook); - if (!notebook) { + + const editorModel = await this._notebookService.modelManager.get(data.notebook); + if (!editorModel) { return null; } - for (let cell of notebook.cells) { + + for (let cell of editorModel.notebook.cells) { if (cell.uri.toString() === resource.toString()) { const bufferFactory: ITextBufferFactory = { create: (defaultEOL) => { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index a46682109fbcacf1be2e63e4d3adc62c5980bd51..d67200912ce775f1c875e30714cfd9b0d1166a59 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -17,13 +17,13 @@ import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { Range } from 'vs/editor/common/core/range'; import { FindMatch, IReadonlyTextBuffer } from 'vs/editor/common/model'; -import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; +import { CellLanguageStatusBarItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; import { CellViewModel, IModelDecorationsChangeAccessor, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { CellKind, IOutput, IRenderOutput, NotebookCellMetadata, NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; -import { CellLanguageStatusBarItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; export const KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED = new RawContextKey('notebookFindWidgetFocused', false); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index a83b8379b259fb3efb46741edb8701859b03761c..a786c472abad4eace8fc5933e413bddb656a0743 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -30,8 +30,9 @@ import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { EditorOptions, IEditorCloseEvent, IEditorMemento } from 'vs/workbench/common/editor'; import { CELL_MARGIN, CELL_RUN_GUTTER, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, EDITOR_BOTTOM_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; import { CellEditState, CellFocusMode, ICellRange, ICellViewModel, INotebookCellList, INotebookEditor, INotebookEditorMouseEvent, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, INotebookEditorContribution, NOTEBOOK_EDITOR_RUNNABLE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { NotebookEditorInput, NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; -import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; +import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; +import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; import { BackLayerWebView } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index 7cfae93b735072fad2caadcd75464b08ea076eb0..01b4c0c7ea3a29866dcae6fe2cc5556e2c182c30 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -3,94 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { EditorInput, EditorModel, IEditorInput, GroupIdentifier, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; -import { Emitter, Event } from 'vs/base/common/event'; -import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; -import { ICell, NotebookCellTextModelSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +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 { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { isEqual } from 'vs/base/common/resources'; -import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities, IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; - -export class NotebookEditorModel extends EditorModel { - private _dirty = false; - - protected readonly _onDidChangeDirty = this._register(new Emitter()); - readonly onDidChangeDirty = this._onDidChangeDirty.event; - - private readonly _onDidChangeCells = new Emitter(); - get onDidChangeCells(): Event { return this._onDidChangeCells.event; } - - private readonly _onDidChangeContent = this._register(new Emitter()); - readonly onDidChangeContent: Event = this._onDidChangeContent.event; - - - get notebook() { - return this._notebook; - } - - constructor( - private _notebook: NotebookTextModel - ) { - super(); - - if (_notebook && _notebook.onDidChangeCells) { - this._register(_notebook.onDidChangeContent(() => { - this._dirty = true; - this._onDidChangeDirty.fire(); - this._onDidChangeContent.fire(); - })); - this._register(_notebook.onDidChangeCells((e) => { - this._onDidChangeCells.fire(e); - })); - } - } - - isDirty() { - return this._dirty; - } - - getNotebook(): NotebookTextModel { - return this._notebook; - } - - insertCell(cell: ICell, index: number) { - let notebook = this.getNotebook(); - - if (notebook) { - this.notebook.insertNewCell(index, [cell as NotebookCellTextModel]); - this._dirty = true; - this._onDidChangeDirty.fire(); - - } - } - - deleteCell(index: number) { - let notebook = this.getNotebook(); - - if (notebook) { - this.notebook.removeCell(index); - } - } - - moveCellToIdx(index: number, newIdx: number) { - this.notebook.moveCellToIdx(index, newIdx); - } - - async save(): Promise { - if (this._notebook) { - this._dirty = false; - this._onDidChangeDirty.fire(); - // todo, flush all states - return true; - } - - return false; - } -} +import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; export class NotebookEditorInput extends EditorInput { @@ -113,36 +32,16 @@ export class NotebookEditorInput extends EditorInput { } static readonly ID: string = 'workbench.input.notebook'; - private promise: Promise | null = null; private textModel: NotebookEditorModel | null = null; - private readonly _onDidChangeContent = this._register(new Emitter()); - readonly onDidChangeContent: Event = this._onDidChangeContent.event; constructor( public resource: URI, public name: string, public readonly viewType: string | undefined, @INotebookService private readonly notebookService: INotebookService, - @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService ) { super(); - - const input = this; - const workingCopyAdapter = new class implements IWorkingCopy { - readonly resource = input.resource.with({ scheme: 'vscode-notebook' }); - get name() { return input.getName(); } - readonly capabilities = input.isUntitled() ? WorkingCopyCapabilities.Untitled : 0; - readonly onDidChangeDirty = input.onDidChangeDirty; - readonly onDidChangeContent = input.onDidChangeContent; - isDirty(): boolean { return input.isDirty(); } - backup(): Promise { return input.backup(); } - save(options?: ISaveOptions): Promise { return input.save(0, options).then(editor => !!editor); } - revert(options?: IRevertOptions): Promise { return input.revert(0, options); } - }; - - this._register(this.workingCopyService.registerWorkingCopy(workingCopyAdapter)); - } getTypeId(): string { @@ -179,7 +78,6 @@ export class NotebookEditorInput extends EditorInput { async save(group: GroupIdentifier, options?: ISaveOptions): Promise { if (this.textModel) { - await this.notebookService.save(this.textModel.notebook.viewType, this.textModel.notebook.uri); await this.textModel.save(); return this; } @@ -187,38 +85,13 @@ export class NotebookEditorInput extends EditorInput { return undefined; } - async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { - if (this.textModel) { - // TODO@rebornix we need hashing - await this.textModel.save(); - } - } - async resolve(): Promise { - if (this.textModel) { - return this.textModel; - } - - if (!this.promise) { - if (!await this.notebookService.canResolve(this.viewType!)) { - throw new Error(`Cannot open notebook of type '${this.viewType}'`); - } - - this.promise = this.notebookService.resolveNotebook(this.viewType!, this.resource).then(notebook => { - this.textModel = new NotebookEditorModel(notebook!); - this.textModel.onDidChangeDirty(() => this._onDidChangeDirty.fire()); - this.textModel.onDidChangeContent(() => { - this._onDidChangeContent.fire(); - }); - return this.textModel; - }); + if (!await this.notebookService.canResolve(this.viewType!)) { + throw new Error(`Cannot open notebook of type '${this.viewType}'`); } - return this.promise; - } - - async backup(): Promise { - throw new Error(); + this.textModel = await this.notebookService.modelManager.resolve(this.resource, this.viewType!); + return this.textModel; } matches(otherInput: unknown): boolean { @@ -235,6 +108,7 @@ export class NotebookEditorInput extends EditorInput { dispose() { if (this.textModel) { this.notebookService.destoryNotebookDocument(this.textModel!.notebook.viewType, this.textModel!.notebook); + this.textModel.dispose(); } super.dispose(); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookService.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts similarity index 83% rename from src/vs/workbench/contrib/notebook/browser/notebookService.ts rename to src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 5aa8c51a855a316591219c825182ced9c338cb50..a429c7c7b6ea22e9b11a56e5852989cd7e8038b6 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; import { notebookProviderExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; @@ -19,46 +19,13 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IEditorService, ICustomEditorViewTypesHandler, ICustomEditorInfo } from 'vs/workbench/services/editor/common/editorService'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { NotebookEditorModelManager } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; +import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService'; function MODEL_ID(resource: URI): string { return resource.toString(); } -export const INotebookService = createDecorator('notebookService'); - -export interface IMainNotebookController { - 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; - save(uri: URI): Promise; -} - -export interface INotebookService { - _serviceBrand: undefined; - canResolve(viewType: string): Promise; - onDidChangeActiveEditor: Event<{ viewType: string, uri: URI }>; - registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): void; - unregisterNotebookProvider(viewType: string): void; - 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; - executeNotebook(viewType: string, uri: URI): Promise; - executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise; - - getContributedNotebookProviders(resource: URI): readonly NotebookProviderInfo[]; - getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined; - getNotebookProviderResourceRoots(): URI[]; - destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void; - updateActiveNotebookDocument(viewType: string, resource: URI): void; - save(viewType: string, resource: URI): Promise; - onDidReceiveMessage(viewType: string, uri: URI, message: any): void; - setToCopy(items: NotebookCellTextModel[]): void; - getToCopy(): NotebookCellTextModel[] | undefined; -} - export class NotebookProviderInfoStore { private readonly contributedEditors = new Map(); @@ -126,8 +93,6 @@ class ModelData implements IDisposable { this._modelEventListeners.dispose(); } } - - export class NotebookService extends Disposable implements INotebookService, ICustomEditorViewTypesHandler { _serviceBrand: undefined; private readonly _notebookProviders = new Map(); @@ -142,13 +107,18 @@ export class NotebookService extends Disposable implements INotebookService, ICu onDidChangeViewTypes: Event = this._onDidChangeViewTypes.event; private cutItems: NotebookCellTextModel[] | undefined; + modelManager: NotebookEditorModelManager; + constructor( @IExtensionService private readonly extensionService: IExtensionService, - @IEditorService private readonly editorService: IEditorService + @IEditorService private readonly editorService: IEditorService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); this._models = {}; + this.modelManager = this.instantiationService.createInstance(NotebookEditorModelManager); + notebookProviderExtensionPoint.setHandler((extensions) => { this.notebookProviderInfoStore.clear(); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 3e0e347568bbd1253265583d48939c75e4d31e77..9cd53cc37ff6d64bf2fbdffce611f9d62da9f027 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -9,7 +9,7 @@ import * as path from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import * as UUID from 'vs/base/common/uuid'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IWebviewService, WebviewElement } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewResourceScheme } from 'vs/workbench/contrib/webview/common/resourceLoader'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts index 521e021e4a7dc80ae62af83d6f1e23c9895deeed..9880e7cb337660b33aedf67f155a0d274c3afa77 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts @@ -12,7 +12,7 @@ import * as nls from 'vs/nls'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { EDITOR_BOTTOM_PADDING, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; import { CellFocusMode, CodeCellRenderTemplate, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { CellOutputKind, IOutput, IRenderOutput, ITransformedDisplayOutputDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index 4df3c39756fd274abe095310325ff2a33120ff95..0dc084c621e9db0cb5697ff906b8fed4fc20403c 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -18,15 +18,15 @@ import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { WorkspaceTextEdit } from 'vs/editor/common/modes'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; import { CellEditState, CellFindMatch, ICellRange, ICellViewModel, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; import { DeleteCellEdit, InsertCellEdit, MoveCellEdit, SpliceCellsEdit } from 'vs/workbench/contrib/notebook/browser/viewModel/cellEdit'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { NotebookEventDispatcher, NotebookMetadataChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; import { CellFoldingState, EditorFoldingStateDelegate } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, INotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { FoldingRegions } from 'vs/editor/contrib/folding/foldingRanges'; export interface INotebookEditorViewState { @@ -241,7 +241,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD constructor( public viewType: string, - private _model: NotebookEditorModel, + private _model: INotebookEditorModel, readonly eventDispatcher: NotebookEventDispatcher, private _layoutInfo: NotebookLayoutInfo | null, @IInstantiationService private readonly instantiationService: IInstantiationService, diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index 48e9ec7d5260b673ae38a506ee2e31e93ef1a707..4374d649994de403c18738f2651c1cc30abfec65 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -77,15 +77,15 @@ export class NotebookCellTextModel implements ICell { } getValue(): string { - const lineCount = this._textBuffer.getLineCount(); - const fullRange = new Range(1, 1, lineCount, this._textBuffer.getLineLength(lineCount) + 1); + const lineCount = this.textBuffer.getLineCount(); + const fullRange = new Range(1, 1, lineCount, this.textBuffer.getLineLength(lineCount) + 1); // todo@rebornix eol? - const eol = this._textBuffer.getEOL(); + const eol = this.textBuffer.getEOL(); if (eol === '\n') { - return this._textBuffer.getValueInRange(fullRange, model.EndOfLinePreference.LF); + return this.textBuffer.getValueInRange(fullRange, model.EndOfLinePreference.LF); } else { - return this._textBuffer.getValueInRange(fullRange, model.EndOfLinePreference.CRLF); + return this.textBuffer.getValueInRange(fullRange, model.EndOfLinePreference.CRLF); } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 92c7b12d5e350e3e28eb3d40339e15dd77a567a1..c2a6945aca9838ab0e9eb4e758893243228e43c2 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -12,6 +12,8 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IEditorModel } from 'vs/platform/editor/common/editor'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; export enum CellKind { Markdown = 1, @@ -462,3 +464,15 @@ export interface ICellEditorViewState { } export const NOTEBOOK_EDITOR_CURSOR_BOUNDARY = new RawContextKey<'none' | 'top' | 'bottom' | 'both'>('notebookEditorCursorAtBoundary', 'none'); + + +export interface INotebookEditorModel extends IEditorModel { + notebook: NotebookTextModel; + onDidChangeCells: Event; + isDirty(): boolean; + getNotebook(): NotebookTextModel; + insertCell(cell: ICell, index: number): void; + deleteCell(index: number): void; + moveCellToIdx(index: number, newIdx: number): void; + save(): Promise; +} diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts new file mode 100644 index 0000000000000000000000000000000000000000..c48b8a6aa744e7cd3dd7871bba14ac2747efacdf --- /dev/null +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -0,0 +1,239 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EditorModel, IRevertOptions } from 'vs/workbench/common/editor'; +import { Emitter, Event } from 'vs/base/common/event'; +import { ICell, NotebookCellTextModelSplice, INotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { ResourceMap } from 'vs/base/common/map'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { URI } from 'vs/base/common/uri'; +import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities, IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { basename } from 'vs/base/common/resources'; + +export interface INotebookEditorModelManager { + models: NotebookEditorModel[]; + + resolve(resource: URI, viewType: string): Promise; + + get(resource: URI): NotebookEditorModel | undefined; +} + + +export class NotebookEditorModel extends EditorModel implements IWorkingCopy, INotebookEditorModel { + private _dirty = false; + protected readonly _onDidChangeDirty = this._register(new Emitter()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; + private readonly _onDidChangeCells = new Emitter(); + get onDidChangeCells(): Event { return this._onDidChangeCells.event; } + private readonly _onDidChangeContent = this._register(new Emitter()); + readonly onDidChangeContent: Event = this._onDidChangeContent.event; + + private _notebook!: NotebookTextModel; + + get notebook() { + return this._notebook; + } + + private _name!: string; + + get name() { + return this._name; + } + + constructor( + public readonly resource: URI, + public readonly viewType: string, + @INotebookService private readonly notebookService: INotebookService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService + ) { + super(); + this._register(this.workingCopyService.registerWorkingCopy(this)); + } + + capabilities: WorkingCopyCapabilities = WorkingCopyCapabilities.Untitled; + + async backup(): Promise { + return {}; + } + + async revert(options?: IRevertOptions | undefined): Promise { + return; + } + + async load(): Promise { + const notebook = await this.notebookService.resolveNotebook(this.viewType!, this.resource); + this._notebook = notebook!; + + this._name = basename(this._notebook!.uri); + + if (this._notebook.onDidChangeCells) { + this._register(this._notebook.onDidChangeContent(() => { + this._dirty = true; + this._onDidChangeDirty.fire(); + this._onDidChangeContent.fire(); + })); + this._register(this._notebook.onDidChangeCells((e) => { + this._onDidChangeCells.fire(e); + })); + } + + return this; + } + + isDirty() { + return this._dirty; + } + + getNotebook(): NotebookTextModel { + return this._notebook; + } + + insertCell(cell: ICell, index: number) { + let notebook = this.getNotebook(); + if (notebook) { + this.notebook.insertNewCell(index, [cell as NotebookCellTextModel]); + this._dirty = true; + this._onDidChangeDirty.fire(); + } + } + + deleteCell(index: number) { + let notebook = this.getNotebook(); + if (notebook) { + this.notebook.removeCell(index); + } + } + + moveCellToIdx(index: number, newIdx: number) { + this.notebook.moveCellToIdx(index, newIdx); + } + + async save(): Promise { + await this.notebookService.save(this.notebook.viewType, this.notebook.uri); + this._dirty = false; + this._onDidChangeDirty.fire(); + return true; + } +} + +export class NotebookEditorModelManager extends Disposable implements INotebookEditorModelManager { + + private readonly mapResourceToModel = new ResourceMap(); + private readonly mapResourceToModelListeners = new ResourceMap(); + private readonly mapResourceToDisposeListener = new ResourceMap(); + private readonly mapResourceToPendingModelLoaders = new ResourceMap>(); + + // private readonly modelLoadQueue = this._register(new ResourceQueue()); + + get models(): NotebookEditorModel[] { + return this.mapResourceToModel.values(); + } + constructor( + @IInstantiationService readonly instantiationService: IInstantiationService + ) { + super(); + } + + async resolve(resource: URI, viewType: string): Promise { + // Return early if model is currently being loaded + const pendingLoad = this.mapResourceToPendingModelLoaders.get(resource); + if (pendingLoad) { + return pendingLoad; + } + + let modelPromise: Promise; + let model = this.get(resource); + // let didCreateModel = false; + + // Model exists + if (model) { + // if (options?.reload) { + // } else { + modelPromise = Promise.resolve(model); + // } + } + + // Model does not exist + else { + // didCreateModel = true; + const newModel = model = this.instantiationService.createInstance(NotebookEditorModel, resource, viewType); + modelPromise = model.load(); + + this.registerModel(newModel); + } + + // Store pending loads to avoid race conditions + this.mapResourceToPendingModelLoaders.set(resource, modelPromise); + + // Make known to manager (if not already known) + this.add(resource, model); + + // dispose and bind new listeners + + try { + const resolvedModel = await modelPromise; + + // Remove from pending loads + this.mapResourceToPendingModelLoaders.delete(resource); + return resolvedModel; + } catch (error) { + // Free resources of this invalid model + if (model) { + model.dispose(); + } + + // Remove from pending loads + this.mapResourceToPendingModelLoaders.delete(resource); + + throw error; + } + } + + add(resource: URI, model: NotebookEditorModel): void { + const knownModel = this.mapResourceToModel.get(resource); + if (knownModel === model) { + return; // already cached + } + + // dispose any previously stored dispose listener for this resource + const disposeListener = this.mapResourceToDisposeListener.get(resource); + if (disposeListener) { + disposeListener.dispose(); + } + + // store in cache but remove when model gets disposed + this.mapResourceToModel.set(resource, model); + this.mapResourceToDisposeListener.set(resource, model.onDispose(() => this.remove(resource))); + } + + remove(resource: URI): void { + this.mapResourceToModel.delete(resource); + + const disposeListener = this.mapResourceToDisposeListener.get(resource); + if (disposeListener) { + dispose(disposeListener); + this.mapResourceToDisposeListener.delete(resource); + } + + const modelListener = this.mapResourceToModelListeners.get(resource); + if (modelListener) { + dispose(modelListener); + this.mapResourceToModelListeners.delete(resource); + } + } + + + private registerModel(model: NotebookEditorModel): void { + + } + + get(resource: URI): NotebookEditorModel | undefined { + return this.mapResourceToModel.get(resource); + } +} diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts new file mode 100644 index 0000000000000000000000000000000000000000..a736beba9823971836bb044222cc219b83bfa049 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +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 { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { INotebookEditorModelManager } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; + +export const INotebookService = createDecorator('notebookService'); + +export interface IMainNotebookController { + 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; + save(uri: URI): Promise; +} + +export interface INotebookService { + _serviceBrand: undefined; + modelManager: INotebookEditorModelManager; + canResolve(viewType: string): Promise; + onDidChangeActiveEditor: Event<{ viewType: string, uri: URI }>; + registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): void; + unregisterNotebookProvider(viewType: string): void; + 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; + executeNotebook(viewType: string, uri: URI): Promise; + executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise; + + getContributedNotebookProviders(resource: URI): readonly NotebookProviderInfo[]; + getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined; + getNotebookProviderResourceRoots(): URI[]; + destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void; + updateActiveNotebookDocument(viewType: string, resource: URI): void; + save(viewType: string, resource: URI): Promise; + onDidReceiveMessage(viewType: string, uri: URI, message: any): void; + setToCopy(items: NotebookCellTextModel[]): void; + getToCopy(): NotebookCellTextModel[] | undefined; +} diff --git a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts index a3eddf949a1158aa9b5f738277376ea846584d82..a1b399c52e605b37ca1c8cf89e757d69055e8aa9 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts @@ -6,10 +6,9 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { CellKind, NotebookCellMetadata, diff } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { withTestNotebook, TestCell } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; +import { withTestNotebook, TestCell, NotebookEditorTestModel } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; @@ -25,7 +24,7 @@ suite('NotebookViewModel', () => { test('ctor', function () { const notebook = new NotebookTextModel(0, 'notebook', URI.parse('test')); - const model = new NotebookEditorModel(notebook); + const model = new NotebookEditorTestModel(notebook); const eventDispatcher = new NotebookEventDispatcher(); const viewModel = new NotebookViewModel('notebook', model, eventDispatcher, null, instantiationService, blukEditService, undoRedoService); assert.equal(viewModel.viewType, 'notebook'); diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index 0c6383297aaaeb65d9c70f7560a91e37d8048b8a..757a2d6b2cd7a9966155ae25f120cf68ef146303 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -4,10 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { CellKind, IOutput, CellUri, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, IOutput, CellUri, NotebookCellMetadata, NotebookCellTextModelSplice, ICell, INotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookViewModel, IModelDecorationsChangeAccessor, CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; import { INotebookEditor, NotebookLayoutInfo, ICellViewModel, ICellRange, INotebookEditorMouseEvent, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; @@ -21,7 +20,7 @@ import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/v import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; - +import { EditorModel } from 'vs/workbench/common/editor'; export class TestCell extends NotebookCellTextModel { constructor( public viewType: string, @@ -206,6 +205,83 @@ export class TestNotebookEditor implements INotebookEditor { // return createCellViewModel(instantiationService, viewType, notebookHandle, mockCell); // } +export class NotebookEditorTestModel extends EditorModel implements INotebookEditorModel { + private _dirty = false; + + protected readonly _onDidChangeDirty = this._register(new Emitter()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; + + private readonly _onDidChangeCells = new Emitter(); + get onDidChangeCells(): Event { return this._onDidChangeCells.event; } + + private readonly _onDidChangeContent = this._register(new Emitter()); + readonly onDidChangeContent: Event = this._onDidChangeContent.event; + + + get notebook() { + return this._notebook; + } + + constructor( + private _notebook: NotebookTextModel + ) { + super(); + + if (_notebook && _notebook.onDidChangeCells) { + this._register(_notebook.onDidChangeContent(() => { + this._dirty = true; + this._onDidChangeDirty.fire(); + this._onDidChangeContent.fire(); + })); + this._register(_notebook.onDidChangeCells((e) => { + this._onDidChangeCells.fire(e); + })); + } + } + + isDirty() { + return this._dirty; + } + + getNotebook(): NotebookTextModel { + return this._notebook; + } + + insertCell(cell: ICell, index: number) { + let notebook = this.getNotebook(); + + if (notebook) { + this.notebook.insertNewCell(index, [cell as NotebookCellTextModel]); + this._dirty = true; + this._onDidChangeDirty.fire(); + + } + } + + deleteCell(index: number) { + let notebook = this.getNotebook(); + + if (notebook) { + this.notebook.removeCell(index); + } + } + + moveCellToIdx(index: number, newIdx: number) { + this.notebook.moveCellToIdx(index, newIdx); + } + + async save(): Promise { + if (this._notebook) { + this._dirty = false; + this._onDidChangeDirty.fire(); + // todo, flush all states + return true; + } + + return false; + } +} + export function withTestNotebook(instantiationService: IInstantiationService, blukEditService: IBulkEditService, undoRedoService: IUndoRedoService, cells: [string[], string, CellKind, IOutput[], NotebookCellMetadata][], callback: (editor: TestNotebookEditor, viewModel: NotebookViewModel, textModel: NotebookTextModel) => void) { const viewType = 'notebook'; const editor = new TestNotebookEditor(); @@ -213,7 +289,7 @@ export function withTestNotebook(instantiationService: IInstantiationService, bl notebook.cells = cells.map((cell, index) => { return new NotebookCellTextModel(notebook.uri, index, cell[0], cell[1], cell[2], cell[3], cell[4]); }); - const model = new NotebookEditorModel(notebook); + const model = new NotebookEditorTestModel(notebook); const eventDispatcher = new NotebookEventDispatcher(); const viewModel = new NotebookViewModel(viewType, model, eventDispatcher, null, instantiationService, blukEditService, undoRedoService);