Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/vs/editor/common/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { IModelContentChange, IModelContentChangedEvent, IModelDecorationsChange
import { IGuidesTextModelPart } from 'vs/editor/common/textModelGuides';
import { ITokenizationTextModelPart } from 'vs/editor/common/tokenizationTextModelPart';
import { ThemeColor } from 'vs/platform/theme/common/themeService';
import { UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo';

/**
* Vertical Lane in the overview ruler of the editor.
Expand Down Expand Up @@ -1023,6 +1024,10 @@ export interface ITextModel {
* @return The cursor state returned by the `cursorStateComputer`.
*/
pushEditOperations(beforeCursorState: Selection[] | null, editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): Selection[] | null;
/**
* @internal
*/
pushEditOperations(beforeCursorState: Selection[] | null, editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer, group?: UndoRedoGroup): Selection[] | null;

/**
* Change the end of line sequence. This is the preferred way of
Expand Down
12 changes: 6 additions & 6 deletions src/vs/editor/common/model/editStack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import { Selection } from 'vs/editor/common/core/selection';
import { EndOfLineSequence, ICursorStateComputer, IValidEditOperation, ITextModel } from 'vs/editor/common/model';
import { TextModel } from 'vs/editor/common/model/textModel';
import { IUndoRedoService, IResourceUndoRedoElement, UndoRedoElementType, IWorkspaceUndoRedoElement } from 'vs/platform/undoRedo/common/undoRedo';
import { IUndoRedoService, IResourceUndoRedoElement, UndoRedoElementType, IWorkspaceUndoRedoElement, UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo';
import { URI } from 'vs/base/common/uri';
import { TextChange, compressConsecutiveTextChanges } from 'vs/editor/common/core/textChange';
import * as buffer from 'vs/base/common/buffer';
Expand Down Expand Up @@ -408,24 +408,24 @@ export class EditStack {
this._undoRedoService.removeElements(this._model.uri);
}

private _getOrCreateEditStackElement(beforeCursorState: Selection[] | null): EditStackElement {
private _getOrCreateEditStackElement(beforeCursorState: Selection[] | null, group: UndoRedoGroup | undefined): EditStackElement {
const lastElement = this._undoRedoService.getLastElement(this._model.uri);
if (isEditStackElement(lastElement) && lastElement.canAppend(this._model)) {
return lastElement;
}
const newElement = new SingleModelEditStackElement(nls.localize('edit', "Typing"), 'undoredo.textBufferEdit', this._model, beforeCursorState);
this._undoRedoService.pushElement(newElement);
this._undoRedoService.pushElement(newElement, group);
return newElement;
}

public pushEOL(eol: EndOfLineSequence): void {
const editStackElement = this._getOrCreateEditStackElement(null);
const editStackElement = this._getOrCreateEditStackElement(null, undefined);
this._model.setEOL(eol);
editStackElement.append(this._model, [], getModelEOL(this._model), this._model.getAlternativeVersionId(), null);
}

public pushEditOperation(beforeCursorState: Selection[] | null, editOperations: ISingleEditOperation[], cursorStateComputer: ICursorStateComputer | null): Selection[] | null {
const editStackElement = this._getOrCreateEditStackElement(beforeCursorState);
public pushEditOperation(beforeCursorState: Selection[] | null, editOperations: ISingleEditOperation[], cursorStateComputer: ICursorStateComputer | null, group?: UndoRedoGroup): Selection[] | null {
const editStackElement = this._getOrCreateEditStackElement(beforeCursorState, group);
const inverseEditOperations = this._model.applyEdits(editOperations, true);
const afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperations);
const textChanges = inverseEditOperations.map((op, index) => ({ index: index, textChange: op.textChange }));
Expand Down
10 changes: 5 additions & 5 deletions src/vs/editor/common/model/textModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelOptions
import { IGuidesTextModelPart } from 'vs/editor/common/textModelGuides';
import { ITokenizationTextModelPart } from 'vs/editor/common/tokenizationTextModelPart';
import { IColorTheme, ThemeColor } from 'vs/platform/theme/common/themeService';
import { IUndoRedoService, ResourceEditStackSnapshot } from 'vs/platform/undoRedo/common/undoRedo';
import { IUndoRedoService, ResourceEditStackSnapshot, UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo';

export function createTextBufferFactory(text: string): model.ITextBufferFactory {
const builder = new PieceTreeTextBufferBuilder();
Expand Down Expand Up @@ -1242,18 +1242,18 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
return result;
}

public pushEditOperations(beforeCursorState: Selection[] | null, editOperations: model.IIdentifiedSingleEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null {
public pushEditOperations(beforeCursorState: Selection[] | null, editOperations: model.IIdentifiedSingleEditOperation[], cursorStateComputer: model.ICursorStateComputer | null, group?: UndoRedoGroup): Selection[] | null {
try {
this._onDidChangeDecorations.beginDeferredEmit();
this._eventEmitter.beginDeferredEmit();
return this._pushEditOperations(beforeCursorState, this._validateEditOperations(editOperations), cursorStateComputer);
return this._pushEditOperations(beforeCursorState, this._validateEditOperations(editOperations), cursorStateComputer, group);
} finally {
this._eventEmitter.endDeferredEmit();
this._onDidChangeDecorations.endDeferredEmit();
}
}

private _pushEditOperations(beforeCursorState: Selection[] | null, editOperations: model.ValidAnnotatedEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null {
private _pushEditOperations(beforeCursorState: Selection[] | null, editOperations: model.ValidAnnotatedEditOperation[], cursorStateComputer: model.ICursorStateComputer | null, group?: UndoRedoGroup): Selection[] | null {
if (this._options.trimAutoWhitespace && this._trimAutoWhitespaceLines) {
// Go through each saved line number and insert a trim whitespace edit
// if it is safe to do so (no conflicts with other edits).
Expand Down Expand Up @@ -1340,7 +1340,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
if (this._initialUndoRedoSnapshot === null) {
this._initialUndoRedoSnapshot = this._undoRedoService.createSnapshot(this.uri);
}
return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer);
return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer, group);
}

_applyUndo(changes: TextChange[], eol: model.EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void {
Expand Down
46 changes: 21 additions & 25 deletions src/vs/workbench/contrib/mergeEditor/browser/model/editing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { equals } from 'vs/base/common/arrays';
import { Range } from 'vs/editor/common/core/range';
import { ITextModel } from 'vs/editor/common/model';
import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
import { LineRange } from './lineRange';

/**
Expand All @@ -22,8 +22,8 @@ export class LineRangeEdit {
return this.range.equals(other.range) && equals(this.newLines, other.newLines);
}

public apply(model: ITextModel): void {
new LineEdits([this]).apply(model);
public toEdits(modelLineCount: number): IIdentifiedSingleEditOperation[] {
return new LineEdits([this]).toEdits(modelLineCount);
}
}

Expand All @@ -41,30 +41,26 @@ export class RangeEdit {
export class LineEdits {
constructor(public readonly edits: readonly LineRangeEdit[]) { }

public apply(model: ITextModel): void {
model.pushEditOperations(
null,
this.edits.map((e) => {
if (e.range.endLineNumberExclusive <= model.getLineCount()) {
return {
range: new Range(e.range.startLineNumber, 1, e.range.endLineNumberExclusive, 1),
text: e.newLines.map(s => s + '\n').join(''),
};
}

if (e.range.startLineNumber === 1) {
return {
range: new Range(1, 1, model.getLineCount(), Number.MAX_SAFE_INTEGER),
text: e.newLines.join('\n'),
};
}
public toEdits(modelLineCount: number): IIdentifiedSingleEditOperation[] {
return this.edits.map((e) => {
if (e.range.endLineNumberExclusive <= modelLineCount) {
return {
range: new Range(e.range.startLineNumber, 1, e.range.endLineNumberExclusive, 1),
text: e.newLines.map(s => s + '\n').join(''),
};
}

if (e.range.startLineNumber === 1) {
return {
range: new Range(e.range.startLineNumber - 1, Number.MAX_SAFE_INTEGER, model.getLineCount(), Number.MAX_SAFE_INTEGER),
text: e.newLines.map(s => '\n' + s).join(''),
range: new Range(1, 1, modelLineCount, Number.MAX_SAFE_INTEGER),
text: e.newLines.join('\n'),
};
}),
() => null
);
}

return {
range: new Range(e.range.startLineNumber - 1, Number.MAX_SAFE_INTEGER, modelLineCount, Number.MAX_SAFE_INTEGER),
text: e.newLines.map(s => '\n' + s).join(''),
};
});
}
}
115 changes: 101 additions & 14 deletions src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
import { CompareResult, equals } from 'vs/base/common/arrays';
import { BugIndicatingError } from 'vs/base/common/errors';
import { autorunHandleChanges, derived, IObservable, IReader, ISettableObservable, ITransaction, keepAlive, observableValue, transaction, waitForState } from 'vs/base/common/observable';
import { URI } from 'vs/base/common/uri';
import { Range } from 'vs/editor/common/core/range';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/model';
import { localize } from 'vs/nls';
import { IResourceUndoRedoElement, IUndoRedoService, UndoRedoElementType, UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo';
import { EditorModel } from 'vs/workbench/common/editor/editorModel';
import { IMergeDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer';
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
Expand Down Expand Up @@ -58,6 +61,7 @@ export class MergeEditorModel extends EditorModel {
public readonly telemetry: MergeEditorTelemetry,
@IModelService private readonly modelService: IModelService,
@ILanguageService private readonly languageService: ILanguageService,
@IUndoRedoService private readonly undoRedoService: IUndoRedoService,
) {
super();

Expand Down Expand Up @@ -405,9 +409,9 @@ export class MergeEditorModel extends EditorModel {
public setState(
baseRange: ModifiedBaseRange,
state: ModifiedBaseRangeState,
markInputAsHandled: boolean | InputNumber,
transaction: ITransaction,
pushStackElement: boolean = false
_markInputAsHandled: boolean | InputNumber,
tx: ITransaction,
_pushStackElement: boolean = false
): void {
if (!this.isUpToDate.get()) {
throw new BugIndicatingError('Cannot set state while updating');
Expand All @@ -421,29 +425,36 @@ export class MergeEditorModel extends EditorModel {
const conflictingDiffs = this.resultTextModelDiffs.findTouchingDiffs(
baseRange.baseRange
);
const group = new UndoRedoGroup();
if (conflictingDiffs) {
this.resultTextModelDiffs.removeDiffs(conflictingDiffs, transaction);
this.resultTextModelDiffs.removeDiffs(conflictingDiffs, tx, group);
}

const { edit, effectiveState } = baseRange.getEditForBase(state);

existingState.accepted.set(effectiveState, transaction);
existingState.accepted.set(effectiveState, tx);
existingState.previousNonDiffingState = undefined;
existingState.computedFromDiffing = false;

const input1Handled = existingState.handledInput1.get();
const input2Handled = existingState.handledInput2.get();

if (!input1Handled || !input2Handled) {
this.undoRedoService.pushElement(
new MarkAsHandledUndoRedoElement(this.resultTextModel.uri, new WeakRef(this), new WeakRef(existingState), input1Handled, input2Handled),
group
);
}

if (edit) {
if (pushStackElement) {
this.resultTextModel.pushStackElement();
}
this.resultTextModelDiffs.applyEditRelativeToOriginal(edit, transaction);
if (pushStackElement) {
this.resultTextModel.pushStackElement();
}
this.resultTextModel.pushStackElement();
this.resultTextModelDiffs.applyEditRelativeToOriginal(edit, tx, group);
this.resultTextModel.pushStackElement();
}

// always set conflict as handled
existingState.handledInput1.set(true, transaction);
existingState.handledInput2.set(true, transaction);
existingState.handledInput1.set(true, tx);
existingState.handledInput2.set(true, tx);
}

public resetDirtyConflictsToBase(): void {
Expand Down Expand Up @@ -474,6 +485,42 @@ export class MergeEditorModel extends EditorModel {
return;
}

const dataRef = new WeakRef(ModifiedBaseRangeData);
const modelRef = new WeakRef(this);

this.undoRedoService.pushElement({
type: UndoRedoElementType.Resource,
resource: this.resultTextModel.uri,
code: 'setInputHandled',
label: localize('setInputHandled', "Set Input Handled"),
redo() {
const model = modelRef.deref();
const data = dataRef.deref();
if (model && !model.isDisposed() && data) {
transaction(tx => {
if (inputNumber === 1) {
state.handledInput1.set(handled, tx);
} else {
state.handledInput2.set(handled, tx);
}
});
}
},
undo() {
const model = modelRef.deref();
const data = dataRef.deref();
if (model && !model.isDisposed() && data) {
transaction(tx => {
if (inputNumber === 1) {
state.handledInput1.set(!handled, tx);
} else {
state.handledInput2.set(!handled, tx);
}
});
}
},
});

if (inputNumber === 1) {
state.handledInput1.set(handled, tx);
} else {
Expand Down Expand Up @@ -723,3 +770,43 @@ export const enum MergeEditorModelState {
upToDate = 2,
updating = 3,
}

class MarkAsHandledUndoRedoElement implements IResourceUndoRedoElement {
public readonly code = 'undoMarkAsHandled';
public readonly label = localize('undoMarkAsHandled', 'Undo Mark As Handled');

public readonly type = UndoRedoElementType.Resource;

constructor(
public readonly resource: URI,
private readonly mergeEditorModelRef: WeakRef<MergeEditorModel>,
private readonly stateRef: WeakRef<ModifiedBaseRangeData>,
private readonly input1Handled: boolean,
private readonly input2Handled: boolean,
) { }

public redo() {
const mergeEditorModel = this.mergeEditorModelRef.deref();
if (!mergeEditorModel || mergeEditorModel.isDisposed()) {
return;
}
const state = this.stateRef.deref();
if (!state) { return; }
transaction(tx => {
state.handledInput1.set(true, tx);
state.handledInput2.set(true, tx);
});
}
public undo() {
const mergeEditorModel = this.mergeEditorModelRef.deref();
if (!mergeEditorModel || mergeEditorModel.isDisposed()) {
return;
}
const state = this.stateRef.deref();
if (!state) { return; }
transaction(tx => {
state.handledInput1.set(this.input1Handled, tx);
state.handledInput2.set(this.input2Handled, tx);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRa
import { ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/utils';
import { IMergeDiffComputer } from './diffComputer';
import { autorun, IObservable, IReader, ITransaction, observableSignal, observableValue, transaction } from 'vs/base/common/observable';
import { UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo';

export class TextModelDiffs extends Disposable {
private recomputeCount = 0;
Expand Down Expand Up @@ -120,7 +121,7 @@ export class TextModelDiffs extends Disposable {
}
}

public removeDiffs(diffToRemoves: DetailedLineRangeMapping[], transaction: ITransaction | undefined): void {
public removeDiffs(diffToRemoves: DetailedLineRangeMapping[], transaction: ITransaction | undefined, group?: UndoRedoGroup): void {
this.ensureUpToDate();

diffToRemoves.sort(compareBy((d) => d.inputRange.startLineNumber, numberComparator));
Expand All @@ -137,7 +138,8 @@ export class TextModelDiffs extends Disposable {
}

this.barrier.runExclusivelyOrThrow(() => {
diffToRemove.getReverseLineEdit().apply(this.textModel);
const edits = diffToRemove.getReverseLineEdit().toEdits(this.textModel.getLineCount());
this.textModel.pushEditOperations(null, edits, () => null, group);
});

diffs = diffs.map((d) =>
Expand All @@ -153,7 +155,7 @@ export class TextModelDiffs extends Disposable {
/**
* Edit must be conflict free.
*/
public applyEditRelativeToOriginal(edit: LineRangeEdit, transaction: ITransaction | undefined): void {
public applyEditRelativeToOriginal(edit: LineRangeEdit, transaction: ITransaction | undefined, group?: UndoRedoGroup): void {
this.ensureUpToDate();

const editMapping = new DetailedLineRangeMapping(
Expand Down Expand Up @@ -191,7 +193,8 @@ export class TextModelDiffs extends Disposable {
}

this.barrier.runExclusivelyOrThrow(() => {
new LineRangeEdit(edit.range.delta(delta), edit.newLines).apply(this.textModel);
const edits = new LineRangeEdit(edit.range.delta(delta), edit.newLines).toEdits(this.textModel.getLineCount());
this.textModel.pushEditOperations(null, edits, () => null, group);
});
this._diffs.set(newDiffs, transaction, TextModelDiffChangeReason.other);
}
Expand Down
Loading