fix output edit sequence.

This commit is contained in:
rebornix 2021-04-24 12:14:23 -07:00
parent 25d716f900
commit 27388ab7cf
No known key found for this signature in database
GPG key ID: 181FC90D15393C20
3 changed files with 114 additions and 17 deletions

View file

@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { flatten } from 'vs/base/common/arrays';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
@ -358,6 +359,14 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
}
private _doApplyEdits(rawEdits: ICellEditOperation[], synchronous: boolean, computeUndoRedo: boolean = true): void {
type TransformedEdit = {
edit: ICellEditOperation;
cellIndex: number;
end: number | undefined;
originalIndex: number;
};
// compress all edits which have no side effects on cell index
const edits = rawEdits.map((edit, index) => {
let cellIndex: number = -1;
if ('index' in edit) {
@ -372,7 +381,6 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
throw new Error('Invalid cell edit');
}
const cell = this._cells[cellIndex];
return {
edit,
cellIndex,
@ -380,8 +388,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
(edit.editType === CellEditType.DocumentMetadata)
? undefined
: (edit.editType === CellEditType.Replace ? edit.index + edit.count : cellIndex),
originalIndex: index,
originalCellOutputsLength: cell?.outputs.length ?? 0
originalIndex: index
};
}).sort((a, b) => {
if (a.end === undefined) {
@ -393,9 +400,40 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
}
return b.end - a.end || b.originalIndex - a.originalIndex;
}).reduce((prev, curr) => {
if (!prev.length) {
// empty
prev.push([curr]);
} else {
const last = prev[prev.length - 1];
const index = last[0].cellIndex;
if (curr.cellIndex === index) {
last.push(curr);
} else {
prev.push([curr]);
}
}
return prev;
}, [] as TransformedEdit[][]).map(editsOnSameIndex => {
const replaceEdits: TransformedEdit[] = [];
const otherEdits: TransformedEdit[] = [];
editsOnSameIndex.forEach(edit => {
if (edit.edit.editType === CellEditType.Replace) {
replaceEdits.push(edit);
} else {
otherEdits.push(edit);
}
});
return [...otherEdits.reverse(), ...replaceEdits];
});
for (const { edit, cellIndex, originalCellOutputsLength } of edits) {
const flattenEdits = flatten(edits);
for (const { edit, cellIndex } of flattenEdits) {
switch (edit.editType) {
case CellEditType.Replace:
this._replaceCells(edit.index, edit.count, edit.cells, synchronous, computeUndoRedo);
@ -404,7 +442,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
this._assertIndex(cellIndex);
const cell = this._cells[cellIndex];
if (edit.append) {
this._spliceNotebookCellOutputs(cell, [[originalCellOutputsLength, 0, edit.outputs.map(op => new NotebookCellOutputTextModel(op))]], computeUndoRedo);
this._spliceNotebookCellOutputs(cell, [[cell.outputs.length, 0, edit.outputs.map(op => new NotebookCellOutputTextModel(op))]], computeUndoRedo);
} else {
this._spliceNotebookCellOutputs2(cell, edit.outputs.map(op => new NotebookCellOutputTextModel(op)), computeUndoRedo);
}

View file

@ -350,12 +350,9 @@ export const enum CellEditType {
Metadata = 3,
CellLanguage = 4,
DocumentMetadata = 5,
OutputsSplice = 6,
Move = 7,
Unknown = 8,
CellContent = 9,
OutputItems = 10,
PartialMetadata = 11
Move = 6,
OutputItems = 7,
PartialMetadata = 8
}
export interface ICellDto2 {

View file

@ -98,7 +98,6 @@ suite('NotebookTextModel', () => {
{ editType: CellEditType.Replace, index: 1, count: 1, cells: [] },
{ editType: CellEditType.Replace, index: 3, count: 0, cells: [new TestCell(viewModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], modeService)] },
], true, undefined, () => undefined, undefined);
assert.strictEqual(textModel.cells.length, 4);
assert.strictEqual(textModel.cells[0].getValue(), 'var a = 1;');
@ -563,11 +562,6 @@ suite('NotebookTextModel', () => {
});
test('Destructive sorting in _doApplyEdits #121994', async function () {
if (1) {
this.skip();
}
await withTestNotebook([
['var a = 1;', 'javascript', CellKind.Code, [{ outputId: 'i42', outputs: [{ mime: 'm/ime', value: 'test' }] }], {}]
], async (editor) => {
@ -596,4 +590,72 @@ suite('NotebookTextModel', () => {
assert.strictEqual(notebook.cells[0].outputs[0].outputs.length, 2);
});
});
test('Destructive sorting in _doApplyEdits #121994. cell splice between output changes', async function () {
await withTestNotebook([
['var a = 1;', 'javascript', CellKind.Code, [{ outputId: 'i42', outputs: [{ mime: 'm/ime', value: 'test' }] }], {}],
['var b = 2;', 'javascript', CellKind.Code, [{ outputId: 'i43', outputs: [{ mime: 'm/ime', value: 'test' }] }], {}],
['var c = 3;', 'javascript', CellKind.Code, [{ outputId: 'i44', outputs: [{ mime: 'm/ime', value: 'test' }] }], {}]
], async (editor) => {
const notebook = editor.viewModel.notebookDocument;
const edits: ICellEditOperation[] = [
{
editType: CellEditType.Output, index: 0, outputs: []
},
{
editType: CellEditType.Replace, index: 1, count: 1, cells: []
},
{
editType: CellEditType.Output, index: 2, append: true, outputs: [{
outputId: 'newOutput',
outputs: [{ mime: 'text/plain', value: 'cba' }, { mime: 'application/foo', value: 'cba' }]
}]
}
];
editor.viewModel.notebookDocument.applyEdits(edits, true, undefined, () => undefined, undefined);
assert.strictEqual(notebook.cells.length, 2);
assert.strictEqual(notebook.cells[0].outputs.length, 0);
assert.strictEqual(notebook.cells[1].outputs.length, 2);
assert.strictEqual(notebook.cells[1].outputs[0].outputId, 'i44');
assert.strictEqual(notebook.cells[1].outputs[1].outputId, 'newOutput');
});
});
test('Destructive sorting in _doApplyEdits #121994. cell splice between output changes 2', async function () {
await withTestNotebook([
['var a = 1;', 'javascript', CellKind.Code, [{ outputId: 'i42', outputs: [{ mime: 'm/ime', value: 'test' }] }], {}],
['var b = 2;', 'javascript', CellKind.Code, [{ outputId: 'i43', outputs: [{ mime: 'm/ime', value: 'test' }] }], {}],
['var c = 3;', 'javascript', CellKind.Code, [{ outputId: 'i44', outputs: [{ mime: 'm/ime', value: 'test' }] }], {}]
], async (editor) => {
const notebook = editor.viewModel.notebookDocument;
const edits: ICellEditOperation[] = [
{
editType: CellEditType.Output, index: 1, append: true, outputs: [{
outputId: 'newOutput',
outputs: [{ mime: 'text/plain', value: 'cba' }, { mime: 'application/foo', value: 'cba' }]
}]
},
{
editType: CellEditType.Replace, index: 1, count: 1, cells: []
},
{
editType: CellEditType.Output, index: 1, append: true, outputs: [{
outputId: 'newOutput2',
outputs: [{ mime: 'text/plain', value: 'cba' }, { mime: 'application/foo', value: 'cba' }]
}]
}
];
editor.viewModel.notebookDocument.applyEdits(edits, true, undefined, () => undefined, undefined);
assert.strictEqual(notebook.cells.length, 2);
assert.strictEqual(notebook.cells[0].outputs.length, 1);
assert.strictEqual(notebook.cells[1].outputs.length, 1);
assert.strictEqual(notebook.cells[1].outputs[0].outputId, 'i44');
});
});
});