diff --git a/.eslintrc.json b/.eslintrc.json index 8761c1c1813..df3c5ad560c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -653,6 +653,20 @@ "**/vs/workbench/services/**/{common,browser}/**" ] }, + { + "target": "**/vs/workbench/contrib/notebook/common/**", + "restrictions": [ + "vs/nls", + "vs/css!./**/*", + "**/vs/base/**/{common,worker}/**", + "**/vs/platform/**/common/**", + "**/vs/editor/**", + "**/vs/workbench/common/**", + "**/vs/workbench/api/common/**", + "**/vs/workbench/services/**/common/**", + "**/vs/workbench/contrib/**/common/**" + ] + }, { "target": "**/vs/workbench/contrib/**/common/**", "restrictions": [ diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index f0087a0b1ef..f80967a2fb1 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -42,8 +42,10 @@ import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { INotebookEditorModelResolverService, NotebookModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { NotebookDiffEditorInput } from './notebookDiffEditorInput'; -import { NotebookDiffEditor } from './notebookDiffEditor'; +import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookDiffEditorInput'; +import { NotebookDiffEditor } from 'vs/workbench/contrib/notebook/browser/notebookDiffEditor'; +import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; +import { NotebookEditorWorkerServiceImpl } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl'; // Editor Contribution @@ -438,6 +440,7 @@ workbenchContributionsRegistry.registerWorkbenchContribution(NotebookContributio workbenchContributionsRegistry.registerWorkbenchContribution(CellContentProvider, LifecyclePhase.Starting); registerSingleton(INotebookService, NotebookService); +registerSingleton(INotebookEditorWorkerService, NotebookEditorWorkerServiceImpl); registerSingleton(INotebookEditorModelResolverService, NotebookModelResolverService, true); const configurationRegistry = Registry.as(Extensions.Configuration); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookDiffEditor.ts index 9d45f6a9fac..40ce3828b8e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookDiffEditor.ts @@ -16,15 +16,13 @@ import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/note import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { NotebookDiffEditorInput } from './notebookDiffEditorInput'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IDiffResult, LcsDiff } from 'vs/base/common/diff/diff'; -import { CellSequence, INotebookDiffEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookDiffEditorModel, INotebookDiffResult } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookDeltaDecoration } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; -import { DiffComputer } from 'vs/editor/common/diff/diffComputer'; import { createDecoration, DECORATIONS, isChangeOrDelete, isChangeOrInsert } from 'vs/editor/browser/widget/diffEditorWidget'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { Color } from 'vs/base/common/color'; @@ -32,6 +30,7 @@ import { IModelDeltaDecoration, IReadonlyTextBuffer } from 'vs/editor/common/mod import { Range } from 'vs/editor/common/core/range'; import { Constants } from 'vs/base/common/uint'; import { defaultInsertColor, defaultRemoveColor, diffInserted, diffRemoved } from 'vs/platform/theme/common/colorRegistry'; +import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; export class NotebookDiffEditor extends BaseEditor { static readonly ID: string = 'workbench.editor.notebookDiffEditor'; @@ -52,6 +51,7 @@ export class NotebookDiffEditor extends BaseEditor { @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, + @INotebookEditorWorkerService private readonly notebookEditorWorkerService: INotebookEditorWorkerService ) { super(NotebookDiffEditor.ID, telemetryService, themeService, storageService); @@ -149,10 +149,9 @@ export class NotebookDiffEditor extends BaseEditor { } private _update(model: INotebookDiffEditorModel) { - const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); - const diffResult = diff.ComputeDiff(false); - - this._adjustHeight(diffResult); + this.notebookEditorWorkerService.computeDiff(model.original.notebook.uri, model.modified.notebook.uri).then(diffResult => { + this._adjustHeight(diffResult); + }); } private _setStrategy(newStrategy: DiffEditorWidgetSideBySide): void { @@ -162,13 +161,12 @@ export class NotebookDiffEditor extends BaseEditor { this._strategy = newStrategy; newStrategy.applyColors(this.themeService.getColorTheme()); - - // if (this._diffComputationResult) { - // this._updateDecorations(); - // } } - private _adjustHeight(diffResult: IDiffResult) { + private _adjustHeight(notebookDiffResult: INotebookDiffResult) { + const diffResult = notebookDiffResult.cellsDiff; + const linesDiffResult = notebookDiffResult.linesDiff; + if (!this._widget || !this._originalWidget) { return; } @@ -211,67 +209,37 @@ export class NotebookDiffEditor extends BaseEditor { }; viewLayoutUpdateDisposables.push(...[ - ...originalCells.map(cell => (cell instanceof CodeCellViewModel) ? cell.onDidChangeLayout(e => update()) : (cell as MarkdownCellViewModel).onDidChangeLayout(e => update())), - ...modifiedCells.map(cell => (cell instanceof CodeCellViewModel) ? cell.onDidChangeLayout(e => update()) : (cell as MarkdownCellViewModel).onDidChangeLayout(e => update())), + ...originalCells.map(cell => (cell instanceof CodeCellViewModel) ? cell.onDidChangeLayout(() => update()) : (cell as MarkdownCellViewModel).onDidChangeLayout(() => update())), + ...modifiedCells.map(cell => (cell instanceof CodeCellViewModel) ? cell.onDidChangeLayout(() => update()) : (cell as MarkdownCellViewModel).onDidChangeLayout(() => update())), ]); + + update(); }); - // console.log(diffResult); - diffResult.changes.forEach(change => { - if (change.modifiedLength === 0) { - // deletion ... + linesDiffResult.forEach(diff => { + const originalCell = this._originalWidget!.viewModel!.viewCells.find(cell => cell.handle === diff.originalCellhandle); + const modifiedCell = this._widget!.viewModel!.viewCells.find(cell => cell.handle === diff.modifiedCellhandle); + + if (!originalCell || !modifiedCell) { return; } - if (change.originalLength === 0) { - // insertion - return; - } + const lineDecorations = this._strategy.getEditorsDiffDecorations(diff.lineChanges, false, false, originalCell.textBuffer, modifiedCell.textBuffer); - for (let i = 0, len = Math.min(change.modifiedLength, change.originalLength); i < len; i++) { - let originalIndex = change.originalStart + i; - let modifiedIndex = change.modifiedStart + i; + this._originalWidget?.changeModelDecorations(accessor => { + accessor.deltaDecorations([], [{ + ownerId: diff.originalCellhandle, + decorations: lineDecorations.original.decorations + }]); + }); - const originalCell = this._originalWidget!.viewModel!.viewCells[originalIndex]; - const modifiedCell = this._widget!.viewModel!.viewCells[modifiedIndex]; - - if (originalCell.getText() !== modifiedCell.getText()) { - // console.log(`original cell ${originalIndex} content change`); - const originalLines = originalCell.textBuffer.getLinesContent(); - const modifiedLines = modifiedCell.textBuffer.getLinesContent(); - const diffComputer = new DiffComputer(originalLines, modifiedLines, { - shouldComputeCharChanges: true, - shouldPostProcessCharChanges: true, - shouldIgnoreTrimWhitespace: false, - shouldMakePrettyDiff: true, - maxComputationTime: 5000 - }); - - const lineChanges = diffComputer.computeDiff().changes; - const lineDecorations = this._strategy.getEditorsDiffDecorations(lineChanges, false, false, originalCell.textBuffer, modifiedCell.textBuffer); - - this._originalWidget?.changeModelDecorations(accessor => { - accessor.deltaDecorations([], [{ - ownerId: originalCell.handle, - decorations: lineDecorations.original.decorations - }]); - }); - - this._widget?.changeModelDecorations(accessor => { - accessor.deltaDecorations([], [{ - ownerId: modifiedCell.handle, - decorations: lineDecorations.modified.decorations - }]); - }); - - // console.log(lineDecorations); - - } else { - // console.log(`original cell ${originalIndex} metadata change`); - } - - } + this._widget?.changeModelDecorations(accessor => { + accessor.deltaDecorations([], [{ + ownerId: diff.modifiedCellhandle, + decorations: lineDecorations.modified.decorations + }]); + }); }); this._originalCellDecorations = this._originalWidget.deltaCellDecorations(this._originalCellDecorations, originalDecorations); @@ -362,15 +330,6 @@ export class DiffEditorWidgetSideBySide extends Disposable { } public getEditorsDiffDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalModel: IReadonlyTextBuffer, modifiedModel: IReadonlyTextBuffer): IEditorsDiffDecorationsWithZones { - // Get view zones - // modifiedWhitespaces = modifiedWhitespaces.sort((a, b) => { - // return a.afterLineNumber - b.afterLineNumber; - // }); - // originalWhitespaces = originalWhitespaces.sort((a, b) => { - // return a.afterLineNumber - b.afterLineNumber; - // }); - // let zones = this._getViewZones(lineChanges, originalWhitespaces, modifiedWhitespaces, originalEditor, modifiedEditor, renderIndicators); - // Get decorations & overview ruler zones let originalDecorations = this._getOriginalEditorDecorations(lineChanges, ignoreTrimWhitespace, renderIndicators, originalModel); let modifiedDecorations = this._getModifiedEditorDecorations(lineChanges, ignoreTrimWhitespace, renderIndicators, modifiedModel); @@ -378,13 +337,9 @@ export class DiffEditorWidgetSideBySide extends Disposable { return { original: { decorations: originalDecorations.decorations, - // overviewZones: originalDecorations.overviewZones, - // zones: zones.original }, modified: { decorations: modifiedDecorations.decorations, - // overviewZones: modifiedDecorations.overviewZones, - // zones: zones.modified } }; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index f56e0eee899..8c6541885cb 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -18,7 +18,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Schemas } from 'vs/base/common/network'; import { IRevertOptions } from 'vs/workbench/common/editor'; import { basename } from 'vs/base/common/path'; -import { ISequence } from 'vs/base/common/diff/diff'; +import { IDiffResult, ISequence } from 'vs/base/common/diff/diff'; export enum CellKind { Markdown = 1, @@ -715,3 +715,8 @@ export class CellSequence implements ISequence { return hashValue; } } + +export interface INotebookDiffResult { + cellsDiff: IDiffResult, + linesDiff: { originalCellhandle: number, modifiedCellhandle: number, lineChanges: editorCommon.ILineChange[] }[]; +} diff --git a/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts b/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts new file mode 100644 index 00000000000..65870613c73 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts @@ -0,0 +1,207 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { ISequence, LcsDiff } from 'vs/base/common/diff/diff'; +import { hash } from 'vs/base/common/hash'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IRequestHandler } from 'vs/base/common/worker/simpleWorker'; +import * as model from 'vs/editor/common/model'; +import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; +import { CellKind, ICellDto2, IMainCellDto, INotebookDiffResult, IProcessedOutput, NotebookCellMetadata, NotebookDataDto, NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { Range } from 'vs/editor/common/core/range'; +import { DiffComputer } from 'vs/editor/common/diff/diffComputer'; +import * as editorCommon from 'vs/editor/common/editorCommon'; +import { EditorWorkerHost } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl'; + +class MirrorCell { + private _textBuffer!: model.IReadonlyTextBuffer; + + get textBuffer() { + if (this._textBuffer) { + return this._textBuffer; + } + + const builder = new PieceTreeTextBufferBuilder(); + builder.acceptChunk(Array.isArray(this._source) ? this._source.join('\n') : this._source); + const bufferFactory = builder.finish(true); + this._textBuffer = bufferFactory.create(model.DefaultEndOfLine.LF); + + return this._textBuffer; + } + + private _hash: number | null = null; + + + constructor( + readonly handle: number, + private _source: string | string[], + readonly language: string, + readonly cellKind: CellKind, + readonly outputs: IProcessedOutput[], + readonly metadata?: NotebookCellMetadata + + ) { } + + getFullModelRange() { + const lineCount = this.textBuffer.getLineCount(); + return new Range(1, 1, lineCount, this.textBuffer.getLineLength(lineCount) + 1); + } + + getValue(): string { + const fullRange = this.getFullModelRange(); + const eol = this.textBuffer.getEOL(); + if (eol === '\n') { + return this.textBuffer.getValueInRange(fullRange, model.EndOfLinePreference.LF); + } else { + return this.textBuffer.getValueInRange(fullRange, model.EndOfLinePreference.CRLF); + } + } + + getHashValue(): number { + if (this._hash !== null) { + return this._hash; + } + + this._hash = hash([hash(this.getValue()), this.metadata]); + // this._hash = hash(this.getValue()); + return this._hash; + } +} + +class MirrorNotebookDocument { + constructor( + readonly uri: URI, + readonly cells: MirrorCell[], + readonly languages: string[], + readonly metadata: NotebookDocumentMetadata, + ) { + } +} + +export class CellSequence implements ISequence { + + constructor(readonly textModel: MirrorNotebookDocument) { + } + + getElements(): string[] | number[] | Int32Array { + const hashValue = new Int32Array(this.textModel.cells.length); + for (let i = 0; i < this.textModel.cells.length; i++) { + hashValue[i] = this.textModel.cells[i].getHashValue(); + } + + return hashValue; + } + + getCellHash(cell: ICellDto2) { + const source = Array.isArray(cell.source) ? cell.source.join('\n') : cell.source; + const hashVal = hash([hash(source), cell.metadata]); + return hashVal; + } +} + +export class NotebookEditorSimpleWorker implements IRequestHandler, IDisposable { + _requestHandlerBrand: any; + + private _models: { [uri: string]: MirrorNotebookDocument; }; + + constructor() { + this._models = Object.create(null); + } + dispose(): void { + } + + public acceptNewModel(uri: string, data: NotebookDataDto): void { + this._models[uri] = new MirrorNotebookDocument(URI.parse(uri), data.cells.map(dto => new MirrorCell( + (dto as IMainCellDto).handle, + dto.source, + dto.language, + dto.cellKind, + dto.outputs, + dto.metadata + )), data.languages, data.metadata); + } + + public acceptRemovedModel(strURL: string): void { + if (!this._models[strURL]) { + return; + } + delete this._models[strURL]; + } + + computeDiff(originalUrl: string, modifiedUrl: string): INotebookDiffResult { + const original = this._getModel(originalUrl); + const modified = this._getModel(modifiedUrl); + + const diff = new LcsDiff(new CellSequence(original), new CellSequence(modified)); + const diffResult = diff.ComputeDiff(false); + + let cellLineChanges: { originalCellhandle: number, modifiedCellhandle: number, lineChanges: editorCommon.ILineChange[] }[] = []; + + diffResult.changes.forEach(change => { + if (change.modifiedLength === 0) { + // deletion ... + return; + } + + if (change.originalLength === 0) { + // insertion + return; + } + + for (let i = 0, len = Math.min(change.modifiedLength, change.originalLength); i < len; i++) { + let originalIndex = change.originalStart + i; + let modifiedIndex = change.modifiedStart + i; + + const originalCell = original.cells[originalIndex]; + const modifiedCell = modified.cells[modifiedIndex]; + + if (originalCell.getValue() !== modifiedCell.getValue()) { + // console.log(`original cell ${originalIndex} content change`); + const originalLines = originalCell.textBuffer.getLinesContent(); + const modifiedLines = modifiedCell.textBuffer.getLinesContent(); + const diffComputer = new DiffComputer(originalLines, modifiedLines, { + shouldComputeCharChanges: true, + shouldPostProcessCharChanges: true, + shouldIgnoreTrimWhitespace: false, + shouldMakePrettyDiff: true, + maxComputationTime: 5000 + }); + + const lineChanges = diffComputer.computeDiff().changes; + + cellLineChanges.push({ + originalCellhandle: originalCell.handle, + modifiedCellhandle: modifiedCell.handle, + lineChanges + }); + + // console.log(lineDecorations); + + } else { + // console.log(`original cell ${originalIndex} metadata change`); + } + + } + }); + + return { + cellsDiff: diffResult, + linesDiff: cellLineChanges + }; + } + + protected _getModel(uri: string): MirrorNotebookDocument { + return this._models[uri]; + } +} + +/** + * Called on the worker side + * @internal + */ +export function create(host: EditorWorkerHost): IRequestHandler { + return new NotebookEditorSimpleWorker(); +} + diff --git a/src/vs/workbench/contrib/notebook/common/services/notebookWorkerService.ts b/src/vs/workbench/contrib/notebook/common/services/notebookWorkerService.ts new file mode 100644 index 00000000000..f85fd6f5cc9 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/common/services/notebookWorkerService.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { ILineChange } from 'vs/editor/common/editorCommon'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { INotebookDiffResult } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + +export const ID_NOTEBOOK_EDITOR_WORKER_SERVICE = 'notebookEditorWorkerService'; +export const INotebookEditorWorkerService = createDecorator(ID_NOTEBOOK_EDITOR_WORKER_SERVICE); + +export interface IDiffComputationResult { + quitEarly: boolean; + identical: boolean; + changes: ILineChange[]; +} + +export interface INotebookEditorWorkerService { + readonly _serviceBrand: undefined; + + canComputeDiff(original: URI, modified: URI): boolean; + computeDiff(original: URI, modified: URI): Promise; +} diff --git a/src/vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl.ts b/src/vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl.ts new file mode 100644 index 00000000000..8f12d9ccda4 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl.ts @@ -0,0 +1,219 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker'; +import { DefaultWorkerFactory } from 'vs/base/worker/defaultWorkerFactory'; +import { INotebookDiffResult } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { NotebookEditorSimpleWorker } from 'vs/workbench/contrib/notebook/common/services/notebookSimpleWorker'; +import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; + +export class NotebookEditorWorkerServiceImpl extends Disposable implements INotebookEditorWorkerService { + declare readonly _serviceBrand: undefined; + + private readonly _workerManager: WorkerManager; + + constructor( + @INotebookService notebookService: INotebookService + ) { + super(); + + this._workerManager = this._register(new WorkerManager(notebookService)); + } + canComputeDiff(original: URI, modified: URI): boolean { + throw new Error('Method not implemented.'); + } + + computeDiff(original: URI, modified: URI): Promise { + return this._workerManager.withWorker().then(client => { + return client.computeDiff(original, modified); + }); + } +} + +export class WorkerManager extends Disposable { + private _editorWorkerClient: NotebookWorkerClient | null; + // private _lastWorkerUsedTime: number; + + constructor( + private readonly _notebookService: INotebookService + ) { + super(); + this._editorWorkerClient = null; + // this._lastWorkerUsedTime = (new Date()).getTime(); + } + + withWorker(): Promise { + // this._lastWorkerUsedTime = (new Date()).getTime(); + if (!this._editorWorkerClient) { + this._editorWorkerClient = new NotebookWorkerClient(this._notebookService, 'notebookEditorWorkerService'); + } + return Promise.resolve(this._editorWorkerClient); + } +} + +export interface IWorkerClient { + getProxyObject(): Promise; + dispose(): void; +} + +export class NotebookEditorModelManager extends Disposable { + private _syncedModels: { [modelUrl: string]: IDisposable; } = Object.create(null); + private _syncedModelsLastUsedTime: { [modelUrl: string]: number; } = Object.create(null); + + constructor( + private readonly _proxy: NotebookEditorSimpleWorker, + private readonly _notebookService: INotebookService + ) { + super(); + } + + public ensureSyncedResources(resources: URI[]): void { + for (const resource of resources) { + let resourceStr = resource.toString(); + + if (!this._syncedModels[resourceStr]) { + this._beginModelSync(resource); + } + if (this._syncedModels[resourceStr]) { + this._syncedModelsLastUsedTime[resourceStr] = (new Date()).getTime(); + } + } + } + + private _beginModelSync(resource: URI): void { + let model = this._notebookService.listNotebookDocuments().find(document => document.uri.toString() === resource.toString()); + if (!model) { + return; + } + + let modelUrl = resource.toString(); + + this._proxy.acceptNewModel( + model.uri.toString(), + { + cells: model.cells.map(cell => ({ + handle: cell.handle, + uri: cell.uri, + source: cell.textBuffer.getLinesContent(), + eol: cell.textBuffer.getEOL(), + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs, + metadata: cell.metadata + })), + languages: model.languages, + metadata: model.metadata + } + ); + + const toDispose = new DisposableStore(); + + // TODO, accept Model change + + // toDispose.add(model.onDidChangeContent((e) => { + // this._proxy.acceptModelChanged(modelUrl.toString(), e); + // })); + toDispose.add(model.onWillDispose(() => { + this._stopModelSync(modelUrl); + })); + toDispose.add(toDisposable(() => { + this._proxy.acceptRemovedModel(modelUrl); + })); + + this._syncedModels[modelUrl] = toDispose; + } + + private _stopModelSync(modelUrl: string): void { + let toDispose = this._syncedModels[modelUrl]; + delete this._syncedModels[modelUrl]; + delete this._syncedModelsLastUsedTime[modelUrl]; + dispose(toDispose); + } +} + +export class EditorWorkerHost { + + private readonly _workerClient: NotebookWorkerClient; + + constructor(workerClient: NotebookWorkerClient) { + this._workerClient = workerClient; + } + + // foreign host request + public fhr(method: string, args: any[]): Promise { + return this._workerClient.fhr(method, args); + } +} + +export class NotebookWorkerClient extends Disposable { + private _worker: IWorkerClient | null; + private readonly _workerFactory: DefaultWorkerFactory; + private _modelManager: NotebookEditorModelManager | null; + + + constructor(private readonly _notebookService: INotebookService, label: string) { + super(); + this._workerFactory = new DefaultWorkerFactory(label); + this._worker = null; + this._modelManager = null; + + } + + // foreign host request + public fhr(method: string, args: any[]): Promise { + throw new Error(`Not implemented!`); + } + + computeDiff(original: URI, modified: URI) { + return this._withSyncedResources([original, modified]).then(proxy => { + return proxy.computeDiff(original.toString(), modified.toString()); + }); + } + + private _getOrCreateModelManager(proxy: NotebookEditorSimpleWorker): NotebookEditorModelManager { + if (!this._modelManager) { + this._modelManager = this._register(new NotebookEditorModelManager(proxy, this._notebookService)); + } + return this._modelManager; + } + + protected _withSyncedResources(resources: URI[]): Promise { + return this._getProxy().then((proxy) => { + this._getOrCreateModelManager(proxy).ensureSyncedResources(resources); + return proxy; + }); + } + + private _getOrCreateWorker(): IWorkerClient { + if (!this._worker) { + try { + this._worker = this._register(new SimpleWorkerClient( + this._workerFactory, + 'vs/workbench/contrib/notebook/common/services/notebookSimpleWorker', + new EditorWorkerHost(this) + )); + } catch (err) { + // logOnceWebWorkerWarning(err); + // this._worker = new SynchronousWorkerClient(new EditorSimpleWorker(new EditorWorkerHost(this), null)); + throw (err); + } + } + return this._worker; + } + + protected _getProxy(): Promise { + return this._getOrCreateWorker().getProxyObject().then(undefined, (err) => { + // logOnceWebWorkerWarning(err); + // this._worker = new SynchronousWorkerClient(new EditorSimpleWorker(new EditorWorkerHost(this), null)); + // return this._getOrCreateWorker().getProxyObject(); + throw (err); + }); + } + + +}