未验证 提交 46dbb797 编写于 作者: A Alex Dima

Compress consecutive edits in undo stack

上级 4fc25c28
...@@ -15,6 +15,7 @@ import { SearchData } from 'vs/editor/common/model/textModelSearch'; ...@@ -15,6 +15,7 @@ import { SearchData } from 'vs/editor/common/model/textModelSearch';
import { LanguageId, LanguageIdentifier, FormattingOptions } from 'vs/editor/common/modes'; import { LanguageId, LanguageIdentifier, FormattingOptions } from 'vs/editor/common/modes';
import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { ThemeColor } from 'vs/platform/theme/common/themeService';
import { MultilineTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore'; import { MultilineTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore';
import { TextChange } from 'vs/editor/common/model/textChange';
/** /**
* Vertical Lane in the overview ruler of the editor. * Vertical Lane in the overview ruler of the editor.
...@@ -373,21 +374,13 @@ export interface IValidEditOperation { ...@@ -373,21 +374,13 @@ export interface IValidEditOperation {
*/ */
range: Range; range: Range;
/** /**
* The text to replace with. This can be null to emulate a simple delete. * The text to replace with. This can be empty to emulate a simple delete.
*/ */
text: string | null; text: string;
/** /**
* This indicates that this operation has "insert" semantics. * @internal
* i.e. forceMoveMarkers = true => if `range` is collapsed, all markers at the position will be moved.
*/ */
forceMoveMarkers: boolean; textChange: TextChange;
}
/**
* @internal
*/
export interface IValidEditOperations {
operations: IValidEditOperation[];
} }
/** /**
...@@ -1106,7 +1099,12 @@ export interface ITextModel { ...@@ -1106,7 +1099,12 @@ export interface ITextModel {
/** /**
* @internal * @internal
*/ */
_applyUndoRedoEdits(edits: IValidEditOperations[], eol: EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): IValidEditOperations[]; _applyUndo(changes: TextChange[], eol: EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void;
/**
* @internal
*/
_applyRedo(changes: TextChange[], eol: EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void;
/** /**
* Undo edit operations until the first previous stop point created by `pushStackElement`. * Undo edit operations until the first previous stop point created by `pushStackElement`.
...@@ -1285,7 +1283,7 @@ export interface ITextBuffer { ...@@ -1285,7 +1283,7 @@ export interface ITextBuffer {
getLineLastNonWhitespaceColumn(lineNumber: number): number; getLineLastNonWhitespaceColumn(lineNumber: number): number;
setEOL(newEOL: '\r\n' | '\n'): void; setEOL(newEOL: '\r\n' | '\n'): void;
applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult; applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean, computeUndoEdits: boolean): ApplyEditsResult;
findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[]; findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[];
} }
......
...@@ -6,11 +6,12 @@ ...@@ -6,11 +6,12 @@
import * as nls from 'vs/nls'; import * as nls from 'vs/nls';
import { onUnexpectedError } from 'vs/base/common/errors'; import { onUnexpectedError } from 'vs/base/common/errors';
import { Selection } from 'vs/editor/common/core/selection'; import { Selection } from 'vs/editor/common/core/selection';
import { EndOfLineSequence, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation, ITextModel, IValidEditOperations } from 'vs/editor/common/model'; import { EndOfLineSequence, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation, ITextModel } from 'vs/editor/common/model';
import { TextModel } from 'vs/editor/common/model/textModel'; import { TextModel } from 'vs/editor/common/model/textModel';
import { IUndoRedoService, IResourceUndoRedoElement, UndoRedoElementType, IWorkspaceUndoRedoElement } from 'vs/platform/undoRedo/common/undoRedo'; import { IUndoRedoService, IResourceUndoRedoElement, UndoRedoElementType, IWorkspaceUndoRedoElement } from 'vs/platform/undoRedo/common/undoRedo';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { getComparisonKey as uriGetComparisonKey } from 'vs/base/common/resources'; import { getComparisonKey as uriGetComparisonKey } from 'vs/base/common/resources';
import { TextChange, compressConsecutiveTextChanges } from 'vs/editor/common/model/textChange';
export class SingleModelEditStackElement implements IResourceUndoRedoElement { export class SingleModelEditStackElement implements IResourceUndoRedoElement {
...@@ -24,7 +25,7 @@ export class SingleModelEditStackElement implements IResourceUndoRedoElement { ...@@ -24,7 +25,7 @@ export class SingleModelEditStackElement implements IResourceUndoRedoElement {
private _afterVersionId: number; private _afterVersionId: number;
private _afterEOL: EndOfLineSequence; private _afterEOL: EndOfLineSequence;
private _afterCursorState: Selection[] | null; private _afterCursorState: Selection[] | null;
private _edits: IValidEditOperations[]; private _changes: TextChange[];
public get resource(): URI { public get resource(): URI {
return this.model.uri; return this.model.uri;
...@@ -40,7 +41,7 @@ export class SingleModelEditStackElement implements IResourceUndoRedoElement { ...@@ -40,7 +41,7 @@ export class SingleModelEditStackElement implements IResourceUndoRedoElement {
this._afterVersionId = this._beforeVersionId; this._afterVersionId = this._beforeVersionId;
this._afterEOL = this._beforeEOL; this._afterEOL = this._beforeEOL;
this._afterCursorState = this._beforeCursorState; this._afterCursorState = this._beforeCursorState;
this._edits = []; this._changes = [];
} }
public setModel(model: ITextModel): void { public setModel(model: ITextModel): void {
...@@ -53,7 +54,7 @@ export class SingleModelEditStackElement implements IResourceUndoRedoElement { ...@@ -53,7 +54,7 @@ export class SingleModelEditStackElement implements IResourceUndoRedoElement {
public append(model: ITextModel, operations: IValidEditOperation[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void { public append(model: ITextModel, operations: IValidEditOperation[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void {
if (operations.length > 0) { if (operations.length > 0) {
this._edits.push({ operations: operations }); this._changes = compressConsecutiveTextChanges(this._changes, operations.map(op => op.textChange));
} }
this._afterEOL = afterEOL; this._afterEOL = afterEOL;
this._afterVersionId = afterVersionId; this._afterVersionId = afterVersionId;
...@@ -66,13 +67,11 @@ export class SingleModelEditStackElement implements IResourceUndoRedoElement { ...@@ -66,13 +67,11 @@ export class SingleModelEditStackElement implements IResourceUndoRedoElement {
public undo(): void { public undo(): void {
this._isOpen = false; this._isOpen = false;
this._edits.reverse(); this.model._applyUndo(this._changes, this._beforeEOL, this._beforeVersionId, this._beforeCursorState);
this._edits = this.model._applyUndoRedoEdits(this._edits, this._beforeEOL, true, false, this._beforeVersionId, this._beforeCursorState);
} }
public redo(): void { public redo(): void {
this._edits.reverse(); this.model._applyRedo(this._changes, this._afterEOL, this._afterVersionId, this._afterCursorState);
this._edits = this.model._applyUndoRedoEdits(this._edits, this._afterEOL, false, true, this._afterVersionId, this._afterCursorState);
} }
} }
......
...@@ -10,6 +10,7 @@ import { ApplyEditsResult, EndOfLinePreference, FindMatch, IInternalModelContent ...@@ -10,6 +10,7 @@ import { ApplyEditsResult, EndOfLinePreference, FindMatch, IInternalModelContent
import { PieceTreeBase, StringBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase'; import { PieceTreeBase, StringBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase';
import { SearchData } from 'vs/editor/common/model/textModelSearch'; import { SearchData } from 'vs/editor/common/model/textModelSearch';
import { countEOL, StringEOL } from 'vs/editor/common/model/tokensStore'; import { countEOL, StringEOL } from 'vs/editor/common/model/tokensStore';
import { TextChange } from 'vs/editor/common/model/textChange';
export interface IValidatedEditOperation { export interface IValidatedEditOperation {
sortIndex: number; sortIndex: number;
...@@ -17,7 +18,7 @@ export interface IValidatedEditOperation { ...@@ -17,7 +18,7 @@ export interface IValidatedEditOperation {
range: Range; range: Range;
rangeOffset: number; rangeOffset: number;
rangeLength: number; rangeLength: number;
text: string | null; text: string;
eolCount: number; eolCount: number;
firstLineLength: number; firstLineLength: number;
lastLineLength: number; lastLineLength: number;
...@@ -205,7 +206,7 @@ export class PieceTreeTextBuffer implements ITextBuffer { ...@@ -205,7 +206,7 @@ export class PieceTreeTextBuffer implements ITextBuffer {
this._pieceTree.setEOL(newEOL); this._pieceTree.setEOL(newEOL);
} }
public applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult { public applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean, computeUndoEdits: boolean): ApplyEditsResult {
let mightContainRTL = this._mightContainRTL; let mightContainRTL = this._mightContainRTL;
let mightContainNonBasicASCII = this._mightContainNonBasicASCII; let mightContainNonBasicASCII = this._mightContainNonBasicASCII;
let canReduceOperations = true; let canReduceOperations = true;
...@@ -225,7 +226,7 @@ export class PieceTreeTextBuffer implements ITextBuffer { ...@@ -225,7 +226,7 @@ export class PieceTreeTextBuffer implements ITextBuffer {
mightContainNonBasicASCII = !strings.isBasicASCII(op.text); mightContainNonBasicASCII = !strings.isBasicASCII(op.text);
} }
let validText: string | null = null; let validText = '';
let eolCount = 0; let eolCount = 0;
let firstLineLength = 0; let firstLineLength = 0;
let lastLineLength = 0; let lastLineLength = 0;
...@@ -260,66 +261,75 @@ export class PieceTreeTextBuffer implements ITextBuffer { ...@@ -260,66 +261,75 @@ export class PieceTreeTextBuffer implements ITextBuffer {
// Sort operations ascending // Sort operations ascending
operations.sort(PieceTreeTextBuffer._sortOpsAscending); 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) { if (canReduceOperations) {
operations = this._reduceOperations(operations); operations = this._reduceOperations(operations);
} }
// Delta encode operations // Delta encode operations
let reverseRanges = PieceTreeTextBuffer._getInverseEditRanges(operations); let reverseRanges = (computeUndoEdits || recordTrimAutoWhitespace ? PieceTreeTextBuffer._getInverseEditRanges(operations) : []);
let newTrimAutoWhitespaceCandidates: { lineNumber: number, oldContent: string }[] = []; let newTrimAutoWhitespaceCandidates: { lineNumber: number, oldContent: string }[] = [];
if (recordTrimAutoWhitespace) {
for (let i = 0; i < operations.length; i++) { for (let i = 0; i < operations.length; i++) {
let op = operations[i]; let op = operations[i];
let reverseRange = reverseRanges[i]; let reverseRange = reverseRanges[i];
if (recordTrimAutoWhitespace && op.isAutoWhitespaceEdit && op.range.isEmpty()) { if (op.isAutoWhitespaceEdit && op.range.isEmpty()) {
// Record already the future line numbers that might be auto whitespace removal candidates on next edit // Record already the future line numbers that might be auto whitespace removal candidates on next edit
for (let lineNumber = reverseRange.startLineNumber; lineNumber <= reverseRange.endLineNumber; lineNumber++) { for (let lineNumber = reverseRange.startLineNumber; lineNumber <= reverseRange.endLineNumber; lineNumber++) {
let currentLineContent = ''; let currentLineContent = '';
if (lineNumber === reverseRange.startLineNumber) { if (lineNumber === reverseRange.startLineNumber) {
currentLineContent = this.getLineContent(op.range.startLineNumber); currentLineContent = this.getLineContent(op.range.startLineNumber);
if (strings.firstNonWhitespaceIndex(currentLineContent) !== -1) { if (strings.firstNonWhitespaceIndex(currentLineContent) !== -1) {
continue; continue;
}
} }
newTrimAutoWhitespaceCandidates.push({ lineNumber: lineNumber, oldContent: currentLineContent });
} }
newTrimAutoWhitespaceCandidates.push({ lineNumber: lineNumber, oldContent: currentLineContent });
} }
} }
} }
let reverseOperations: IReverseSingleEditOperation[] = []; let reverseOperations: IReverseSingleEditOperation[] = [];
for (let i = 0; i < operations.length; i++) { if (computeUndoEdits) {
let op = operations[i];
let reverseRange = reverseRanges[i];
reverseOperations[i] = { let hasTouchingRanges = false;
sortIndex: op.sortIndex, for (let i = 0, count = operations.length - 1; i < count; i++) {
identifier: op.identifier, let rangeEnd = operations[i].range.getEndPosition();
range: reverseRange, let nextRangeStart = operations[i + 1].range.getStartPosition();
text: this.getValueInRange(op.range),
forceMoveMarkers: op.forceMoveMarkers if (nextRangeStart.isBeforeOrEqual(rangeEnd)) {
}; if (nextRangeStart.isBefore(rangeEnd)) {
} // overlapping ranges
throw new Error('Overlapping ranges are not allowed!');
}
hasTouchingRanges = true;
}
}
// Can only sort reverse operations when the order is not significant let reverseRangeDeltaOffset = 0;
if (!hasTouchingRanges) { for (let i = 0; i < operations.length; i++) {
reverseOperations.sort((a, b) => a.sortIndex - b.sortIndex); const op = operations[i];
const reverseRange = reverseRanges[i];
const bufferText = this.getValueInRange(op.range);
const reverseRangeOffset = op.rangeOffset + reverseRangeDeltaOffset;
reverseRangeDeltaOffset += (op.text.length - bufferText.length);
reverseOperations[i] = {
sortIndex: op.sortIndex,
identifier: op.identifier,
range: reverseRange,
text: bufferText,
textChange: new TextChange(op.rangeOffset, bufferText, reverseRangeOffset, op.text)
};
}
// Can only sort reverse operations when the order is not significant
if (!hasTouchingRanges) {
reverseOperations.sort((a, b) => a.sortIndex - b.sortIndex);
}
} }
this._mightContainRTL = mightContainRTL; this._mightContainRTL = mightContainRTL;
this._mightContainNonBasicASCII = mightContainNonBasicASCII; this._mightContainNonBasicASCII = mightContainNonBasicASCII;
...@@ -393,7 +403,7 @@ export class PieceTreeTextBuffer implements ITextBuffer { ...@@ -393,7 +403,7 @@ export class PieceTreeTextBuffer implements ITextBuffer {
result.push(this.getValueInRange(new Range(lastEndLineNumber, lastEndColumn, range.startLineNumber, range.startColumn))); result.push(this.getValueInRange(new Range(lastEndLineNumber, lastEndColumn, range.startLineNumber, range.startColumn)));
// (2) -- Push new text // (2) -- Push new text
if (operation.text) { if (operation.text.length > 0) {
result.push(operation.text); result.push(operation.text);
} }
...@@ -433,17 +443,15 @@ export class PieceTreeTextBuffer implements ITextBuffer { ...@@ -433,17 +443,15 @@ export class PieceTreeTextBuffer implements ITextBuffer {
const endLineNumber = op.range.endLineNumber; const endLineNumber = op.range.endLineNumber;
const endColumn = op.range.endColumn; const endColumn = op.range.endColumn;
if (startLineNumber === endLineNumber && startColumn === endColumn && (!op.text || op.text.length === 0)) { if (startLineNumber === endLineNumber && startColumn === endColumn && op.text.length === 0) {
// no-op // no-op
continue; continue;
} }
const text = op.text ? op.text : ''; if (op.text) {
if (text) {
// replacement // replacement
this._pieceTree.delete(op.rangeOffset, op.rangeLength); this._pieceTree.delete(op.rangeOffset, op.rangeLength);
this._pieceTree.insert(op.rangeOffset, text, true); this._pieceTree.insert(op.rangeOffset, op.text, true);
} else { } else {
// deletion // deletion
...@@ -454,7 +462,7 @@ export class PieceTreeTextBuffer implements ITextBuffer { ...@@ -454,7 +462,7 @@ export class PieceTreeTextBuffer implements ITextBuffer {
contentChanges.push({ contentChanges.push({
range: contentChangeRange, range: contentChangeRange,
rangeLength: op.rangeLength, rangeLength: op.rangeLength,
text: text, text: op.text,
rangeOffset: op.rangeOffset, rangeOffset: op.rangeOffset,
forceMoveMarkers: op.forceMoveMarkers forceMoveMarkers: op.forceMoveMarkers
}); });
...@@ -503,7 +511,7 @@ export class PieceTreeTextBuffer implements ITextBuffer { ...@@ -503,7 +511,7 @@ export class PieceTreeTextBuffer implements ITextBuffer {
let resultRange: Range; let resultRange: Range;
if (op.text && op.text.length > 0) { if (op.text.length > 0) {
// the operation inserts something // the operation inserts something
const lineCount = op.eolCount + 1; const lineCount = op.eolCount + 1;
......
...@@ -4,35 +4,33 @@ ...@@ -4,35 +4,33 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
export class TextChange { export class TextChange {
public readonly oldPosition: number;
public readonly oldLength: number;
public readonly oldEnd: number;
public readonly oldText: string;
public readonly newPosition: number;
public readonly newLength: number;
public readonly newEnd: number;
public readonly newText: string;
constructor( public get oldLength(): number {
oldPosition: number, return this.oldText.length;
oldText: string, }
newPosition: number,
newText: string public get oldEnd(): number {
) { return this.oldPosition + this.oldText.length;
this.oldPosition = oldPosition; }
this.oldLength = oldText.length;
this.oldEnd = this.oldPosition + this.oldLength; public get newLength(): number {
this.oldText = oldText; return this.newText.length;
this.newPosition = newPosition;
this.newLength = newText.length;
this.newEnd = this.newPosition + this.newLength;
this.newText = newText;
} }
public get newEnd(): number {
return this.newPosition + this.newText.length;
}
constructor(
public readonly oldPosition: number,
public readonly oldText: string,
public readonly newPosition: number,
public readonly newText: string
) { }
} }
export function compressConsecutiveTextChanges(prevEdits: TextChange[] | null, currEdits: TextChange[]): TextChange[] { export function compressConsecutiveTextChanges(prevEdits: TextChange[] | null, currEdits: TextChange[]): TextChange[] {
if (prevEdits === null) { if (prevEdits === null || prevEdits.length === 0) {
return currEdits; return currEdits;
} }
const compressor = new TextChangeCompressor(prevEdits, currEdits); const compressor = new TextChangeCompressor(prevEdits, currEdits);
......
...@@ -37,6 +37,7 @@ import { Color } from 'vs/base/common/color'; ...@@ -37,6 +37,7 @@ import { Color } from 'vs/base/common/color';
import { Constants } from 'vs/base/common/uint'; import { Constants } from 'vs/base/common/uint';
import { EditorTheme } from 'vs/editor/common/view/viewContext'; import { EditorTheme } from 'vs/editor/common/view/viewContext';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { TextChange } from 'vs/editor/common/model/textChange';
function createTextBufferBuilder() { function createTextBufferBuilder() {
return new PieceTreeTextBufferBuilder(); return new PieceTreeTextBufferBuilder();
...@@ -1283,19 +1284,39 @@ export class TextModel extends Disposable implements model.ITextModel { ...@@ -1283,19 +1284,39 @@ export class TextModel extends Disposable implements model.ITextModel {
return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer); return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer);
} }
_applyUndoRedoEdits(edits: model.IValidEditOperations[], eol: model.EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): model.IValidEditOperations[] { _applyUndo(changes: TextChange[], eol: model.EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void {
const edits = changes.map<model.IIdentifiedSingleEditOperation>((change) => {
const rangeStart = this.getPositionAt(change.newPosition);
const rangeEnd = this.getPositionAt(change.newEnd);
return {
range: new Range(rangeStart.lineNumber, rangeStart.column, rangeEnd.lineNumber, rangeEnd.column),
text: change.oldText
};
});
this._applyUndoRedoEdits(edits, eol, true, false, resultingAlternativeVersionId, resultingSelection);
}
_applyRedo(changes: TextChange[], eol: model.EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void {
const edits = changes.map<model.IIdentifiedSingleEditOperation>((change) => {
const rangeStart = this.getPositionAt(change.oldPosition);
const rangeEnd = this.getPositionAt(change.oldEnd);
return {
range: new Range(rangeStart.lineNumber, rangeStart.column, rangeEnd.lineNumber, rangeEnd.column),
text: change.newText
};
});
this._applyUndoRedoEdits(edits, eol, false, true, resultingAlternativeVersionId, resultingSelection);
}
private _applyUndoRedoEdits(edits: model.IIdentifiedSingleEditOperation[], eol: model.EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void {
try { try {
this._onDidChangeDecorations.beginDeferredEmit(); this._onDidChangeDecorations.beginDeferredEmit();
this._eventEmitter.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit();
this._isUndoing = isUndoing; this._isUndoing = isUndoing;
this._isRedoing = isRedoing; this._isRedoing = isRedoing;
let reverseEdits: model.IValidEditOperations[] = []; this.applyEdits(edits, false);
for (let i = 0, len = edits.length; i < len; i++) {
reverseEdits[i] = { operations: this.applyEdits(edits[i].operations) };
}
this.setEOL(eol); this.setEOL(eol);
this._overwriteAlternativeVersionId(resultingAlternativeVersionId); this._overwriteAlternativeVersionId(resultingAlternativeVersionId);
return reverseEdits;
} finally { } finally {
this._isUndoing = false; this._isUndoing = false;
this._isRedoing = false; this._isRedoing = false;
...@@ -1304,21 +1325,21 @@ export class TextModel extends Disposable implements model.ITextModel { ...@@ -1304,21 +1325,21 @@ export class TextModel extends Disposable implements model.ITextModel {
} }
} }
public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[]): model.IValidEditOperation[] { public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[], computeUndoEdits: boolean = true): model.IValidEditOperation[] {
try { try {
this._onDidChangeDecorations.beginDeferredEmit(); this._onDidChangeDecorations.beginDeferredEmit();
this._eventEmitter.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit();
return this._doApplyEdits(this._validateEditOperations(rawOperations)); return this._doApplyEdits(this._validateEditOperations(rawOperations), computeUndoEdits);
} finally { } finally {
this._eventEmitter.endDeferredEmit(); this._eventEmitter.endDeferredEmit();
this._onDidChangeDecorations.endDeferredEmit(); this._onDidChangeDecorations.endDeferredEmit();
} }
} }
private _doApplyEdits(rawOperations: model.ValidAnnotatedEditOperation[]): model.IValidEditOperation[] { private _doApplyEdits(rawOperations: model.ValidAnnotatedEditOperation[], computeUndoEdits: boolean): model.IValidEditOperation[] {
const oldLineCount = this._buffer.getLineCount(); const oldLineCount = this._buffer.getLineCount();
const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace); const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace, computeUndoEdits);
const newLineCount = this._buffer.getLineCount(); const newLineCount = this._buffer.getLineCount();
const contentChanges = result.changes; const contentChanges = result.changes;
......
...@@ -1348,7 +1348,7 @@ suite('Editor Controller - Regression tests', () => { ...@@ -1348,7 +1348,7 @@ suite('Editor Controller - Regression tests', () => {
CoreEditingCommands.Undo.runEditorCommand(null, editor, null); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world '); assert.equal(model.getLineContent(1), 'Hello world ');
assertCursor(cursor, new Position(1, 13)); assertCursor(cursor, new Selection(1, 12, 1, 13));
CoreEditingCommands.Undo.runEditorCommand(null, editor, null); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world'); assert.equal(model.getLineContent(1), 'Hello world');
......
...@@ -54,7 +54,7 @@ for (let fileSize of fileSizes) { ...@@ -54,7 +54,7 @@ for (let fileSize of fileSizes) {
fn: (textBuffer) => { fn: (textBuffer) => {
// for line model, this loop doesn't reflect the real situation. // for line model, this loop doesn't reflect the real situation.
for (const edit of edits) { for (const edit of edits) {
textBuffer.applyEdits([edit], false); textBuffer.applyEdits([edit], false, false);
} }
} }
}); });
...@@ -67,7 +67,7 @@ for (let fileSize of fileSizes) { ...@@ -67,7 +67,7 @@ for (let fileSize of fileSizes) {
}, },
preCycle: (textBuffer) => { preCycle: (textBuffer) => {
for (const edit of edits) { for (const edit of edits) {
textBuffer.applyEdits([edit], false); textBuffer.applyEdits([edit], false, false);
} }
return textBuffer; return textBuffer;
}, },
...@@ -91,7 +91,7 @@ for (let fileSize of fileSizes) { ...@@ -91,7 +91,7 @@ for (let fileSize of fileSizes) {
}, },
preCycle: (textBuffer) => { preCycle: (textBuffer) => {
for (const edit of edits) { for (const edit of edits) {
textBuffer.applyEdits([edit], false); textBuffer.applyEdits([edit], false, false);
} }
return textBuffer; return textBuffer;
}, },
...@@ -121,7 +121,7 @@ for (let fileSize of fileSizes) { ...@@ -121,7 +121,7 @@ for (let fileSize of fileSizes) {
}, },
preCycle: (textBuffer) => { preCycle: (textBuffer) => {
for (const edit of edits) { for (const edit of edits) {
textBuffer.applyEdits([edit], false); textBuffer.applyEdits([edit], false, false);
} }
return textBuffer; return textBuffer;
}, },
...@@ -134,4 +134,4 @@ for (let fileSize of fileSizes) { ...@@ -134,4 +134,4 @@ for (let fileSize of fileSizes) {
editsSuite.run(); editsSuite.run();
} }
} }
\ No newline at end of file
...@@ -41,10 +41,10 @@ for (let fileSize of fileSizes) { ...@@ -41,10 +41,10 @@ for (let fileSize of fileSizes) {
return textBuffer; return textBuffer;
}, },
fn: (textBuffer) => { fn: (textBuffer) => {
textBuffer.applyEdits(edits.slice(0, i), false); textBuffer.applyEdits(edits.slice(0, i), false, false);
} }
}); });
} }
replaceSuite.run(); replaceSuite.run();
} }
\ No newline at end of file
...@@ -36,8 +36,8 @@ export function testApplyEditsWithSyncedModels(original: string[], edits: IIdent ...@@ -36,8 +36,8 @@ export function testApplyEditsWithSyncedModels(original: string[], edits: IIdent
identifier: edit.identifier, identifier: edit.identifier,
range: edit.range, range: edit.range,
text: edit.text, text: edit.text,
forceMoveMarkers: edit.forceMoveMarkers, forceMoveMarkers: edit.forceMoveMarkers || false,
isAutoWhitespaceEdit: edit.isAutoWhitespaceEdit isAutoWhitespaceEdit: edit.isAutoWhitespaceEdit || false
}; };
}; };
// Assert the inverse of the inverse edits are the original edits // Assert the inverse of the inverse edits are the original edits
......
...@@ -18,7 +18,7 @@ suite('PieceTreeTextBuffer._getInverseEdits', () => { ...@@ -18,7 +18,7 @@ suite('PieceTreeTextBuffer._getInverseEdits', () => {
range: new Range(startLineNumber, startColumn, endLineNumber, endColumn), range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),
rangeOffset: 0, rangeOffset: 0,
rangeLength: 0, rangeLength: 0,
text: text ? text.join('\n') : null, text: text ? text.join('\n') : '',
eolCount: text ? text.length - 1 : 0, eolCount: text ? text.length - 1 : 0,
firstLineLength: text ? text[0].length : 0, firstLineLength: text ? text[0].length : 0,
lastLineLength: text ? text[text.length - 1].length : 0, lastLineLength: text ? text[text.length - 1].length : 0,
...@@ -272,7 +272,7 @@ suite('PieceTreeTextBuffer._toSingleEditOperation', () => { ...@@ -272,7 +272,7 @@ suite('PieceTreeTextBuffer._toSingleEditOperation', () => {
range: new Range(startLineNumber, startColumn, endLineNumber, endColumn), range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),
rangeOffset: rangeOffset, rangeOffset: rangeOffset,
rangeLength: rangeLength, rangeLength: rangeLength,
text: text ? text.join('\n') : null, text: text ? text.join('\n') : '',
eolCount: text ? text.length - 1 : 0, eolCount: text ? text.length - 1 : 0,
firstLineLength: text ? text[0].length : 0, firstLineLength: text ? text[0].length : 0,
lastLineLength: text ? text[text.length - 1].length : 0, lastLineLength: text ? text[text.length - 1].length : 0,
......
...@@ -71,8 +71,8 @@ suite('Editor Model - Model Edit Operation', () => { ...@@ -71,8 +71,8 @@ suite('Editor Model - Model Edit Operation', () => {
identifier: edit.identifier, identifier: edit.identifier,
range: edit.range, range: edit.range,
text: edit.text, text: edit.text,
forceMoveMarkers: edit.forceMoveMarkers, forceMoveMarkers: edit.forceMoveMarkers || false,
isAutoWhitespaceEdit: edit.isAutoWhitespaceEdit isAutoWhitespaceEdit: edit.isAutoWhitespaceEdit || false
}; };
}; };
assert.deepEqual(originalOp.map(simplifyEdit), editOp.map(simplifyEdit)); assert.deepEqual(originalOp.map(simplifyEdit), editOp.map(simplifyEdit));
......
...@@ -1552,14 +1552,9 @@ declare namespace monaco.editor { ...@@ -1552,14 +1552,9 @@ declare namespace monaco.editor {
*/ */
range: Range; range: Range;
/** /**
* The text to replace with. This can be null to emulate a simple delete. * The text to replace with. This can be empty to emulate a simple delete.
*/ */
text: string | null; text: string;
/**
* This indicates that this operation has "insert" semantics.
* i.e. forceMoveMarkers = true => if `range` is collapsed, all markers at the position will be moved.
*/
forceMoveMarkers: boolean;
} }
/** /**
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册