Skip to content

Commit

Permalink
Merge pull request #157723 from microsoft/rebornix/nb-document-hash
Browse files Browse the repository at this point in the history
Minimal document edits for file content change
  • Loading branch information
rebornix committed Aug 10, 2022
2 parents adbd449 + 4185d52 commit ed3c693
Show file tree
Hide file tree
Showing 5 changed files with 380 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,22 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod

this._register(this.model.onDidChangeOutputs((splice) => {
const removedOutputs: ICellOutputViewModel[] = [];
let outputLayoutChange = false;
for (let i = splice.start; i < splice.start + splice.deleteCount; i++) {
if (this._outputCollection[i] !== undefined && this._outputCollection[i] !== 0) {
outputLayoutChange = true;
}
}

this._outputCollection.splice(splice.start, splice.deleteCount, ...splice.newOutputs.map(() => 0));
removedOutputs.push(...this._outputViewModels.splice(splice.start, splice.deleteCount, ...splice.newOutputs.map(output => new CellOutputViewModel(this, output, this._notebookService))));

this._outputsTop = null;
this._onDidChangeOutputs.fire(splice);
this._onDidRemoveOutputs.fire(removedOutputs);
this.layoutChange({ outputHeight: true }, 'CodeCellViewModel#model.onDidChangeOutputs');
if (outputLayoutChange) {
this.layoutChange({ outputHeight: true }, 'CodeCellViewModel#model.onDidChangeOutputs');
}
dispose(removedOutputs);
}));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as DOM from 'vs/base/browser/dom';
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ICellViewModel, INotebookEditorDelegate, KERNEL_EXTENSIONS } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
Expand Down Expand Up @@ -102,9 +103,15 @@ export class NotebookEditorContextKeys {
this._hasOutputs.set(hasOutputs);
};

const layoutDisposable = this._viewModelDisposables.add(new DisposableStore());

const addCellOutputsListener = (c: ICellViewModel) => {
return c.model.onDidChangeOutputs(() => {
recomputeOutputsExistence();
layoutDisposable.clear();

layoutDisposable.add(DOM.scheduleAtNextAnimationFrame(() => {
recomputeOutputsExistence();
}));
});
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { Emitter, Event } from 'vs/base/common/event';
import { hash } from 'vs/base/common/hash';
import { hash, StringSHA1 } from 'vs/base/common/hash';
import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import * as UUID from 'vs/base/common/uuid';
Expand All @@ -16,7 +16,7 @@ import { TextModel } from 'vs/editor/common/model/textModel';
import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { NotebookCellOutputTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel';
import { CellInternalMetadataChangedEvent, CellKind, ICell, ICellOutput, IOutputDto, IOutputItemDto, NotebookCellCollapseState, NotebookCellInternalMetadata, NotebookCellMetadata, NotebookCellOutputsSplice, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellInternalMetadataChangedEvent, CellKind, ICell, ICellDto2, ICellOutput, IOutputDto, IOutputItemDto, NotebookCellCollapseState, NotebookCellInternalMetadata, NotebookCellMetadata, NotebookCellOutputsSplice, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';

export class NotebookCellTextModel extends Disposable implements ICell {
private readonly _onDidChangeOutputs = this._register(new Emitter<NotebookCellOutputsSplice>());
Expand Down Expand Up @@ -143,6 +143,7 @@ export class NotebookCellTextModel extends Disposable implements ICell {
return this._textBuffer;
}

private _textBufferHash: string | null = null;
private _hash: number | null = null;

private _versionId: number = 1;
Expand Down Expand Up @@ -224,12 +225,27 @@ export class NotebookCellTextModel extends Disposable implements ICell {
}
}

getTextBufferHash() {
if (this._textBufferHash !== null) {
return this._textBufferHash;
}

const shaComputer = new StringSHA1();
const snapshot = this.textBuffer.createSnapshot(false);
let text: string | null;
while ((text = snapshot.read())) {
shaComputer.update(text);
}
this._textBufferHash = shaComputer.digest();
return this._textBufferHash;
}

getHashValue(): number {
if (this._hash !== null) {
return this._hash;
}

this._hash = hash([hash(this.language), hash(this.getValue()), this._getPersisentMetadata(), this.transientOptions.transientOutputs ? [] : this._outputs.map(op => ({
this._hash = hash([hash(this.language), this.getTextBufferHash(), this._getPersisentMetadata(), this.transientOptions.transientOutputs ? [] : this._outputs.map(op => ({
outputs: op.outputs.map(output => ({
mime: output.mime,
data: Array.from(output.data.buffer)
Expand Down Expand Up @@ -333,6 +349,42 @@ export class NotebookCellTextModel extends Disposable implements ICell {
return this.getHashValue() === b.getHashValue();
}

/**
* Only compares
* - language
* - mime
* - cellKind
* - internal metadata
* - source
*/
fastEqual(b: ICellDto2): boolean {
if (this.language !== b.language) {
return false;
}

if (this.mime !== b.mime) {
return false;
}

if (this.cellKind !== b.cellKind) {
return false;
}

if (this.internalMetadata?.executionOrder !== b.internalMetadata?.executionOrder
|| this.internalMetadata?.lastRunSuccess !== b.internalMetadata?.lastRunSuccess
|| this.internalMetadata?.runStartTime !== b.internalMetadata?.runStartTime
|| this.internalMetadata?.runStartTimeAdjustment !== b.internalMetadata?.runStartTimeAdjustment
|| this.internalMetadata?.runEndTime !== b.internalMetadata?.runEndTime) {
return false;
}

if (this._source !== b.source) {
return false;
}

return true;
}

override dispose() {
dispose(this._outputs);
// Manually release reference to previous text buffer to avoid large leaks
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,10 +391,11 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel

reset(cells: ICellDto2[], metadata: NotebookDocumentMetadata, transientOptions: TransientOptions): void {
this.transientOptions = transientOptions;
this._cellhandlePool = 0;
const edits = NotebookTextModel.computeEdits(this, cells);

this.applyEdits(
[
{ editType: CellEditType.Replace, index: 0, count: this.cells.length, cells },
...edits,
{ editType: CellEditType.DocumentMetadata, metadata }
],
true,
Expand All @@ -404,6 +405,84 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
);
}

static computeEdits(model: NotebookTextModel, cells: ICellDto2[]) {
const edits: ICellEditOperation[] = [];

const commonPrefix = this._commonPrefix(model.cells, model.cells.length, 0, cells, cells.length, 0);

if (commonPrefix > 0) {
for (let i = 0; i < commonPrefix; i++) {
edits.push(
{
editType: CellEditType.Metadata,
index: i,
metadata: cells[i].metadata ?? {}
},
{
editType: CellEditType.Output,
index: i,
outputs: cells[i].outputs,
append: false
}
);
}
}

if (model.cells.length === cells.length && commonPrefix === model.cells.length) {
return edits;
}

const commonSuffix = this._commonSuffix(model.cells, model.cells.length - commonPrefix, commonPrefix, cells, cells.length - commonPrefix, commonPrefix);

if (commonSuffix > 0) {
edits.push({ editType: CellEditType.Replace, index: commonPrefix, count: model.cells.length - commonPrefix - commonSuffix, cells: cells.slice(commonPrefix, cells.length - commonSuffix) });
} else if (commonPrefix > 0) {
edits.push({ editType: CellEditType.Replace, index: commonPrefix, count: model.cells.length - commonPrefix, cells: cells.slice(commonPrefix) });
} else {
edits.push({ editType: CellEditType.Replace, index: 0, count: model.cells.length, cells });
}

if (commonSuffix > 0) {
// has same suffix
for (let i = commonSuffix; i > 0; i--) {
edits.push(
{
editType: CellEditType.Metadata,
index: model.cells.length - i,
metadata: cells[cells.length - i].metadata ?? {}
},
{
editType: CellEditType.Output,
index: model.cells.length - i,
outputs: cells[cells.length - i].outputs,
append: false
}
);
}
}

return edits;
}

private static _commonPrefix(a: readonly NotebookCellTextModel[], aLen: number, aDelta: number, b: ICellDto2[], bLen: number, bDelta: number): number {
const maxResult = Math.min(aLen, bLen);
let result = 0;
for (let i = 0; i < maxResult && a[aDelta + i].fastEqual(b[bDelta + i]); i++) {
result++;
}

return result;
}

private static _commonSuffix(a: readonly NotebookCellTextModel[], aLen: number, aDelta: number, b: ICellDto2[], bLen: number, bDelta: number): number {
const maxResult = Math.min(aLen, bLen);
let result = 0;
for (let i = 0; i < maxResult && a[aDelta + aLen - i - 1].fastEqual(b[bDelta + bLen - i - 1]); i++) {
result++;
}
return result;
}

applyEdits(rawEdits: ICellEditOperation[], synchronous: boolean, beginSelectionState: ISelectionState | undefined, endSelectionsComputer: () => ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined, computeUndoRedo: boolean): boolean {
this._pauseableEmitter.pause();
this.pushStackElement('edit', beginSelectionState, undoRedoGroup);
Expand Down

0 comments on commit ed3c693

Please sign in to comment.