diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 3afd6a6662f391d6d2bb40875c4142ce3ebeeaae..ff16a9a93ccfb64122d88603f6ee19d83ff38b81 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -1092,9 +1092,11 @@ export interface ITextModel { * Edit the model without adding the edits to the undo stack. * This can have dire consequences on the undo stack! See @pushEditOperations for the preferred way. * @param operations The edit operations. - * @return The inverse edit operations, that, when applied, will bring the model back to the previous state. + * @return If desired, the inverse edit operations, that, when applied, will bring the model back to the previous state. */ - applyEdits(operations: IIdentifiedSingleEditOperation[]): IValidEditOperation[]; + applyEdits(operations: IIdentifiedSingleEditOperation[]): void; + applyEdits(operations: IIdentifiedSingleEditOperation[], computeUndoEdits: false): void; + applyEdits(operations: IIdentifiedSingleEditOperation[], computeUndoEdits: true): IValidEditOperation[]; /** * Change the end of line sequence without recording in the undo stack. @@ -1299,7 +1301,7 @@ export interface ITextBuffer { export class ApplyEditsResult { constructor( - public readonly reverseEdits: IValidEditOperation[], + public readonly reverseEdits: IValidEditOperation[] | null, public readonly changes: IInternalModelContentChange[], public readonly trimAutoWhitespaceLineNumbers: number[] | null ) { } diff --git a/src/vs/editor/common/model/editStack.ts b/src/vs/editor/common/model/editStack.ts index 66cf765204262502048fdf753fc13a8b566e5ef7..349065e0870d0192e5715dfda08ee3a929c3ade1 100644 --- a/src/vs/editor/common/model/editStack.ts +++ b/src/vs/editor/common/model/editStack.ts @@ -326,7 +326,7 @@ export class EditStack { public pushEditOperation(beforeCursorState: Selection[] | null, editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer | null): Selection[] | null { const editStackElement = this._getOrCreateEditStackElement(beforeCursorState); - const inverseEditOperations = this._model.applyEdits(editOperations); + const inverseEditOperations = this._model.applyEdits(editOperations, true); const afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperations); editStackElement.append(this._model, inverseEditOperations, getModelEOL(this._model), this._model.getAlternativeVersionId(), afterCursorState); return afterCursorState; diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts index e56041e1e3f498231e785aec66c1897bdbf99cbd..3dd4acda9d69f8f259a018ef632ef0e827b23b31 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts @@ -261,6 +261,20 @@ export class PieceTreeTextBuffer implements ITextBuffer { // Sort operations ascending operations.sort(PieceTreeTextBuffer._sortOpsAscending); + let hasTouchingRanges = false; + for (let i = 0, count = operations.length - 1; i < count; i++) { + let rangeEnd = operations[i].range.getEndPosition(); + let nextRangeStart = operations[i + 1].range.getStartPosition(); + + if (nextRangeStart.isBeforeOrEqual(rangeEnd)) { + if (nextRangeStart.isBefore(rangeEnd)) { + // overlapping ranges + throw new Error('Overlapping ranges are not allowed!'); + } + hasTouchingRanges = true; + } + } + if (canReduceOperations) { operations = this._reduceOperations(operations); } @@ -289,24 +303,11 @@ export class PieceTreeTextBuffer implements ITextBuffer { } } - let reverseOperations: IReverseSingleEditOperation[] = []; + let reverseOperations: IReverseSingleEditOperation[] | null = null; if (computeUndoEdits) { - let hasTouchingRanges = false; - for (let i = 0, count = operations.length - 1; i < count; i++) { - let rangeEnd = operations[i].range.getEndPosition(); - let nextRangeStart = operations[i + 1].range.getStartPosition(); - - if (nextRangeStart.isBeforeOrEqual(rangeEnd)) { - if (nextRangeStart.isBefore(rangeEnd)) { - // overlapping ranges - throw new Error('Overlapping ranges are not allowed!'); - } - hasTouchingRanges = true; - } - } - let reverseRangeDeltaOffset = 0; + reverseOperations = []; for (let i = 0; i < operations.length; i++) { const op = operations[i]; const reverseRange = reverseRanges[i]; diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 31a929f0aa47eb245b468d268cbed5991ee02817..73a171cefeeec1fa7b45b608f0ccd0b6f3223888 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -1330,18 +1330,22 @@ export class TextModel extends Disposable implements model.ITextModel { } } - public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[], computeUndoEdits: boolean = true): model.IValidEditOperation[] { + public applyEdits(operations: model.IIdentifiedSingleEditOperation[]): void; + public applyEdits(operations: model.IIdentifiedSingleEditOperation[], computeUndoEdits: false): void; + public applyEdits(operations: model.IIdentifiedSingleEditOperation[], computeUndoEdits: true): model.IValidEditOperation[]; + public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[], computeUndoEdits: boolean = false): void | model.IValidEditOperation[] { try { this._onDidChangeDecorations.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit(); - return this._doApplyEdits(this._validateEditOperations(rawOperations), computeUndoEdits); + const operations = this._validateEditOperations(rawOperations); + return this._doApplyEdits(operations, computeUndoEdits); } finally { this._eventEmitter.endDeferredEmit(); this._onDidChangeDecorations.endDeferredEmit(); } } - private _doApplyEdits(rawOperations: model.ValidAnnotatedEditOperation[], computeUndoEdits: boolean): model.IValidEditOperation[] { + private _doApplyEdits(rawOperations: model.ValidAnnotatedEditOperation[], computeUndoEdits: boolean): void | model.IValidEditOperation[] { const oldLineCount = this._buffer.getLineCount(); const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace, computeUndoEdits); @@ -1419,7 +1423,7 @@ export class TextModel extends Disposable implements model.ITextModel { ); } - return result.reverseEdits; + return (result.reverseEdits === null ? undefined : result.reverseEdits); } public undo(): void { diff --git a/src/vs/editor/test/common/model/editableTextModel.test.ts b/src/vs/editor/test/common/model/editableTextModel.test.ts index b37a3b9820ea846bee83aa7780d19879a6a227fe..67c8a119e30c8f66de633eaa47fadb9ae465d74b 100644 --- a/src/vs/editor/test/common/model/editableTextModel.test.ts +++ b/src/vs/editor/test/common/model/editableTextModel.test.ts @@ -1104,7 +1104,7 @@ suite('EditorModel - EditableTextModel.applyEdits', () => { { range: new Range(3, 1, 3, 6), text: null, }, { range: new Range(2, 1, 3, 1), text: null, }, { range: new Range(3, 6, 3, 6), text: '\nline2' } - ]); + ], true); model.applyEdits(undoEdits); diff --git a/src/vs/editor/test/common/model/editableTextModelTestUtils.ts b/src/vs/editor/test/common/model/editableTextModelTestUtils.ts index 2ec1be624c0addbaef74989821251318e5e4bd73..9531a8bb1af68882965d3098611eba1b451a4de7 100644 --- a/src/vs/editor/test/common/model/editableTextModelTestUtils.ts +++ b/src/vs/editor/test/common/model/editableTextModelTestUtils.ts @@ -17,7 +17,7 @@ export function testApplyEditsWithSyncedModels(original: string[], edits: IIdent assertSyncedModels(originalStr, (model, assertMirrorModels) => { // Apply edits & collect inverse edits - let inverseEdits = model.applyEdits(edits); + let inverseEdits = model.applyEdits(edits, true); // Assert edits produced expected result assert.deepEqual(model.getValue(EndOfLinePreference.LF), expectedStr); @@ -25,7 +25,7 @@ export function testApplyEditsWithSyncedModels(original: string[], edits: IIdent assertMirrorModels(); // Apply the inverse edits - let inverseInverseEdits = model.applyEdits(inverseEdits); + let inverseInverseEdits = model.applyEdits(inverseEdits, true); // Assert the inverse edits brought back model to original state assert.deepEqual(model.getValue(EndOfLinePreference.LF), originalStr); diff --git a/src/vs/editor/test/common/model/model.test.ts b/src/vs/editor/test/common/model/model.test.ts index 77ce4133e46f684bd4b9d65bc968bfb37e7c56b3..ed6787bc5287ce8aa6170a81644ebae96e1854ef 100644 --- a/src/vs/editor/test/common/model/model.test.ts +++ b/src/vs/editor/test/common/model/model.test.ts @@ -330,7 +330,7 @@ suite('Editor Model - Model', () => { let res = thisModel.applyEdits([ { range: new Range(2, 1, 2, 1), text: 'a' }, { range: new Range(1, 1, 1, 1), text: 'b' }, - ]); + ], true); assert.deepEqual(res[0].range, new Range(2, 1, 2, 2)); assert.deepEqual(res[1].range, new Range(1, 1, 1, 2)); diff --git a/src/vs/editor/test/common/model/modelEditOperation.test.ts b/src/vs/editor/test/common/model/modelEditOperation.test.ts index 1c6cc88081b01845643a5b20641ba91edfbcdc04..058c5d3d56a671f9d473223c7aa891d3ed141091 100644 --- a/src/vs/editor/test/common/model/modelEditOperation.test.ts +++ b/src/vs/editor/test/common/model/modelEditOperation.test.ts @@ -50,14 +50,14 @@ suite('Editor Model - Model Edit Operation', () => { function assertSingleEditOp(singleEditOp: IIdentifiedSingleEditOperation, editedLines: string[]) { let editOp = [singleEditOp]; - let inverseEditOp = model.applyEdits(editOp); + let inverseEditOp = model.applyEdits(editOp, true); assert.equal(model.getLineCount(), editedLines.length); for (let i = 0; i < editedLines.length; i++) { assert.equal(model.getLineContent(i + 1), editedLines[i]); } - let originalOp = model.applyEdits(inverseEditOp); + let originalOp = model.applyEdits(inverseEditOp, true); assert.equal(model.getLineCount(), 5); assert.equal(model.getLineContent(1), LINE1); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 8273afe16bbecab45024814fec31f4d5d47fdb82..8bbf1b7068b81a2ca0863f2e3fef9ed5ba1b80ea 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1902,9 +1902,11 @@ declare namespace monaco.editor { * Edit the model without adding the edits to the undo stack. * This can have dire consequences on the undo stack! See @pushEditOperations for the preferred way. * @param operations The edit operations. - * @return The inverse edit operations, that, when applied, will bring the model back to the previous state. + * @return If desired, the inverse edit operations, that, when applied, will bring the model back to the previous state. */ - applyEdits(operations: IIdentifiedSingleEditOperation[]): IValidEditOperation[]; + applyEdits(operations: IIdentifiedSingleEditOperation[]): void; + applyEdits(operations: IIdentifiedSingleEditOperation[], computeUndoEdits: false): void; + applyEdits(operations: IIdentifiedSingleEditOperation[], computeUndoEdits: true): IValidEditOperation[]; /** * Change the end of line sequence without recording in the undo stack. * This can have dire consequences on the undo stack! See @pushEOL for the preferred way. diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts index c7916e7de2db478868d0f08a37d79883a3622bd3..4a9b99b697158a3be37344c07431b7bdcf76bc72 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -399,7 +399,7 @@ export class BulkEditPreviewProvider implements ITextModelContentProvider { } // apply new edits and keep (future) undo edits const newEdits = this._operations.getFileEdits(uri); - const newUndoEdits = model.applyEdits(newEdits); + const newUndoEdits = model.applyEdits(newEdits, true); this._modelPreviewEdits.set(model.id, newUndoEdits); }