Skip to content

Commit 470b6ef

Browse files
authored
diff editor v2: sync update (microsoft#185039)
1 parent 4b939d0 commit 470b6ef

File tree

5 files changed

+116
-56
lines changed

5 files changed

+116
-56
lines changed

src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,6 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
306306
if (!m) { return; }
307307

308308
const movedText = m.diff.get()!.movedTexts.find(m => m.lineRangeMapping.originalRange.contains(e.position.lineNumber));
309-
310309
m.syncedMovedTexts.set(movedText, undefined);
311310
}));
312311
return editor;
@@ -322,7 +321,6 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
322321
if (!m) { return; }
323322

324323
const movedText = m.diff.get()!.movedTexts.find(m => m.lineRangeMapping.modifiedRange.contains(e.position.lineNumber));
325-
326324
m.syncedMovedTexts.set(movedText, undefined);
327325
}));
328326
// Revert change when an arrow is clicked.

src/vs/editor/browser/widget/diffEditorWidget2/diffModel.ts

Lines changed: 92 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,30 @@ import { RunOnceScheduler } from 'vs/base/common/async';
77
import { Disposable } from 'vs/base/common/lifecycle';
88
import { IObservable, IReader, ITransaction, derived, observableSignal, observableSignalFromEvent, observableValue, transaction } from 'vs/base/common/observable';
99
import { autorunWithStore2 } from 'vs/base/common/observableImpl/autorun';
10+
import { isDefined } from 'vs/base/common/types';
1011
import { LineRange } from 'vs/editor/common/core/lineRange';
1112
import { Range } from 'vs/editor/common/core/range';
1213
import { IDocumentDiff, IDocumentDiffProvider } from 'vs/editor/common/diff/documentDiffProvider';
13-
import { LineRangeMapping, MovedText, RangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
14+
import { LineRangeMapping, MovedText, RangeMapping, SimpleLineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
1415
import { lineRangeMappingFromRangeMappings } from 'vs/editor/common/diff/standardLinesDiffComputer';
1516
import { IDiffEditorModel } from 'vs/editor/common/editorCommon';
1617
import { ITextModel } from 'vs/editor/common/model';
1718
import { TextEditInfo } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper';
1819
import { combineTextEditInfos } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/combineTextEditInfos';
19-
import { lengthAdd, lengthDiffNonNegative, lengthOfRange, lengthToPosition, lengthZero, positionToLength } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length';
20+
import { lengthAdd, lengthDiffNonNegative, lengthGetLineCount, lengthOfRange, lengthToPosition, lengthZero, positionToLength } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length';
2021

2122
export class DiffModel extends Disposable {
2223
private readonly _isDiffUpToDate = observableValue<boolean>('isDiffUpToDate', false);
2324
public readonly isDiffUpToDate: IObservable<boolean> = this._isDiffUpToDate;
2425

26+
private _lastDiff: IDocumentDiff | undefined;
2527
private readonly _diff = observableValue<DiffState | undefined>('diff', undefined);
2628
public readonly diff: IObservable<DiffState | undefined> = this._diff;
2729

28-
private readonly _unchangedRegions = observableValue<{ regions: UnchangedRegion[]; originalDecorationIds: string[]; modifiedDecorationIds: string[] }>('unchangedRegion', { regions: [], originalDecorationIds: [], modifiedDecorationIds: [] });
30+
private readonly _unchangedRegions = observableValue<{ regions: UnchangedRegion[]; originalDecorationIds: string[]; modifiedDecorationIds: string[] }>(
31+
'unchangedRegion',
32+
{ regions: [], originalDecorationIds: [], modifiedDecorationIds: [] }
33+
);
2934
public readonly unchangedRegions: IObservable<UnchangedRegion[]> = derived('unchangedRegions', r =>
3035
this._hideUnchangedRegions.read(r) ? this._unchangedRegions.read(r).regions : []
3136
);
@@ -50,23 +55,28 @@ export class DiffModel extends Disposable {
5055
if (!diff) {
5156
return;
5257
}
53-
/*const textEdits = TextEditInfo.fromModelContentChanges(e.changes);
54-
this._diff.set(
55-
applyModifiedEdits(diff, textEdits, model.original, model.modified),
56-
undefined
57-
);*/
58+
if (!this._showMoves.get()) {
59+
const textEdits = TextEditInfo.fromModelContentChanges(e.changes);
60+
this._lastDiff = applyModifiedEdits(this._lastDiff!, textEdits, model.original, model.modified);
61+
this._diff.set(DiffState.fromDiffResult(this._lastDiff), undefined);
62+
const currentSyncedMovedText = this.syncedMovedTexts.get();
63+
this.syncedMovedTexts.set(currentSyncedMovedText ? this._lastDiff.moves.find(m => m.lineRangeMapping.modifiedRange.intersect(currentSyncedMovedText.lineRangeMapping.modifiedRange)) : undefined, undefined);
64+
}
65+
5866
debouncer.schedule();
5967
}));
6068
this._register(model.original.onDidChangeContent((e) => {
6169
const diff = this._diff.get();
6270
if (!diff) {
6371
return;
6472
}
65-
/*const textEdits = TextEditInfo.fromModelContentChanges(e.changes);
66-
this._diff.set(
67-
applyOriginalEdits(diff, textEdits, model.original, model.modified),
68-
undefined
69-
);*/
73+
if (!this._showMoves.get()) {
74+
const textEdits = TextEditInfo.fromModelContentChanges(e.changes);
75+
this._lastDiff = applyOriginalEdits(this._lastDiff!, textEdits, model.original, model.modified);
76+
this._diff.set(DiffState.fromDiffResult(this._lastDiff), undefined);
77+
const currentSyncedMovedText = this.syncedMovedTexts.get();
78+
this.syncedMovedTexts.set(currentSyncedMovedText ? this._lastDiff.moves.find(m => m.lineRangeMapping.modifiedRange.intersect(currentSyncedMovedText.lineRangeMapping.modifiedRange)) : undefined, undefined);
79+
}
7080
debouncer.schedule();
7181
}));
7282

@@ -137,8 +147,11 @@ export class DiffModel extends Disposable {
137147
);
138148

139149
transaction(tx => {
150+
this._lastDiff = result;
140151
this._diff.set(DiffState.fromDiffResult(result), tx);
141152
this._isDiffUpToDate.set(true, tx);
153+
const currentSyncedMovedText = this.syncedMovedTexts.get();
154+
this.syncedMovedTexts.set(currentSyncedMovedText ? this._lastDiff.moves.find(m => m.lineRangeMapping.modifiedRange.intersect(currentSyncedMovedText.lineRangeMapping.modifiedRange)) : undefined, tx);
142155

143156
this._unchangedRegions.set(
144157
{
@@ -152,7 +165,7 @@ export class DiffModel extends Disposable {
152165
}));
153166
}
154167

155-
public revealModifiedLine(lineNumber: number, tx: ITransaction): void {
168+
public ensureModifiedLineIsVisible(lineNumber: number, tx: ITransaction): void {
156169
const unchangedRegions = this._unchangedRegions.get().regions;
157170
for (const r of unchangedRegions) {
158171
if (r.getHiddenModifiedRange(undefined).contains(lineNumber)) {
@@ -162,7 +175,7 @@ export class DiffModel extends Disposable {
162175
}
163176
}
164177

165-
public revealOriginalLine(lineNumber: number, tx: ITransaction): void {
178+
public ensureOriginalLineIsVisible(lineNumber: number, tx: ITransaction): void {
166179
const unchangedRegions = this._unchangedRegions.get().regions;
167180
for (const r of unchangedRegions) {
168181
if (r.getHiddenOriginalRange(undefined).contains(lineNumber)) {
@@ -315,47 +328,82 @@ function applyOriginalEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], orig
315328
return diff;
316329
}
317330

318-
const diffTextEdits = diff.changes.flatMap(c => c.innerChanges!.map(c => new TextEditInfo(
319-
positionToLength(c.modifiedRange.getStartPosition()),
320-
positionToLength(c.modifiedRange.getEndPosition()),
321-
lengthOfRange(c.originalRange).toLength(),
322-
)));
331+
const diff2 = flip(diff);
332+
const diff3 = applyModifiedEdits(diff2, textEdits, modifiedTextModel, originalTextModel);
333+
return flip(diff3);
334+
}
323335

324-
const combined = combineTextEditInfos(diffTextEdits, textEdits);
336+
function flip(diff: IDocumentDiff): IDocumentDiff {
337+
return {
338+
changes: diff.changes.map(c => c.flip()),
339+
moves: diff.moves.map(m => m.flip()),
340+
identical: diff.identical,
341+
quitEarly: diff.quitEarly,
342+
};
343+
}
325344

326-
let lastModifiedEndOffset = lengthZero;
327-
let lastOriginalEndOffset = lengthZero;
328-
const rangeMappings = combined.map(c => {
329-
const originalStartOffset = lengthAdd(lastOriginalEndOffset, lengthDiffNonNegative(lastModifiedEndOffset, c.startOffset));
330-
lastModifiedEndOffset = c.endOffset;
331-
lastOriginalEndOffset = lengthAdd(originalStartOffset, c.newLength);
345+
function applyModifiedEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], originalTextModel: ITextModel, modifiedTextModel: ITextModel): IDocumentDiff {
346+
if (textEdits.length === 0) {
347+
return diff;
348+
}
332349

333-
return new RangeMapping(
334-
Range.fromPositions(lengthToPosition(originalStartOffset), lengthToPosition(lastOriginalEndOffset)),
335-
Range.fromPositions(lengthToPosition(c.startOffset), lengthToPosition(c.endOffset)),
336-
);
337-
});
350+
const changes = applyModifiedEditsToLineRangeMappings(diff.changes, textEdits, originalTextModel, modifiedTextModel);
338351

339-
const changes = lineRangeMappingFromRangeMappings(
340-
rangeMappings,
341-
originalTextModel.getLinesContent(),
342-
modifiedTextModel.getLinesContent(),
343-
);
352+
const moves = diff.moves.map(m => {
353+
const newModifiedRange = applyEditToLineRange(m.lineRangeMapping.modifiedRange, textEdits);
354+
return newModifiedRange ? new MovedText(
355+
new SimpleLineRangeMapping(m.lineRangeMapping.originalRange, newModifiedRange),
356+
applyModifiedEditsToLineRangeMappings(m.changes, textEdits, originalTextModel, modifiedTextModel),
357+
) : undefined;
358+
}).filter(isDefined);
344359

345360
return {
346361
identical: false,
347362
quitEarly: false,
348363
changes,
349-
moves: [],
364+
moves,
350365
};
351366
}
352367

353-
function applyModifiedEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], originalTextModel: ITextModel, modifiedTextModel: ITextModel): IDocumentDiff {
354-
if (textEdits.length === 0) {
355-
return diff;
368+
function applyEditToLineRange(range: LineRange, textEdits: TextEditInfo[]): LineRange | undefined {
369+
let rangeStartLineNumber = range.startLineNumber;
370+
let rangeEndLineNumberEx = range.endLineNumberExclusive;
371+
372+
for (let i = textEdits.length - 1; i >= 0; i--) {
373+
const textEdit = textEdits[i];
374+
const textEditStartLineNumber = lengthGetLineCount(textEdit.startOffset) + 1;
375+
const textEditEndLineNumber = lengthGetLineCount(textEdit.endOffset) + 1;
376+
const newLengthLineCount = lengthGetLineCount(textEdit.newLength);
377+
const delta = newLengthLineCount - (textEditEndLineNumber - textEditStartLineNumber);
378+
379+
if (textEditEndLineNumber < rangeStartLineNumber) {
380+
// the text edit is before us
381+
rangeStartLineNumber += delta;
382+
rangeEndLineNumberEx += delta;
383+
} else if (textEditStartLineNumber > rangeEndLineNumberEx) {
384+
// the text edit is after us
385+
// NOOP
386+
} else if (textEditStartLineNumber < rangeStartLineNumber && rangeEndLineNumberEx < textEditEndLineNumber) {
387+
// the range is fully contained in the text edit
388+
return undefined;
389+
} else if (textEditStartLineNumber < rangeStartLineNumber && textEditEndLineNumber <= rangeEndLineNumberEx) {
390+
// the text edit ends inside our range
391+
rangeStartLineNumber = textEditEndLineNumber + 1;
392+
rangeStartLineNumber += delta;
393+
rangeEndLineNumberEx += delta;
394+
} else if (rangeStartLineNumber <= textEditStartLineNumber && textEditEndLineNumber < rangeStartLineNumber) {
395+
// the text edit starts inside our range
396+
rangeEndLineNumberEx = textEditStartLineNumber;
397+
} else {
398+
rangeEndLineNumberEx += delta;
399+
}
356400
}
357401

358-
const diffTextEdits = diff.changes.flatMap(c => c.innerChanges!.map(c => new TextEditInfo(
402+
return new LineRange(rangeStartLineNumber, rangeEndLineNumberEx);
403+
}
404+
405+
function applyModifiedEditsToLineRangeMappings(changes: readonly LineRangeMapping[], textEdits: TextEditInfo[], originalTextModel: ITextModel, modifiedTextModel: ITextModel): LineRangeMapping[] {
406+
const diffTextEdits = changes.flatMap(c => c.innerChanges!.map(c => new TextEditInfo(
359407
positionToLength(c.originalRange.getStartPosition()),
360408
positionToLength(c.originalRange.getEndPosition()),
361409
lengthOfRange(c.modifiedRange).toLength(),
@@ -376,16 +424,10 @@ function applyModifiedEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], orig
376424
);
377425
});
378426

379-
const changes = lineRangeMappingFromRangeMappings(
427+
const newChanges = lineRangeMappingFromRangeMappings(
380428
rangeMappings,
381429
originalTextModel.getLinesContent(),
382430
modifiedTextModel.getLinesContent(),
383431
);
384-
385-
return {
386-
identical: false,
387-
quitEarly: false,
388-
changes,
389-
moves: [],
390-
};
432+
return newChanges;
391433
}

src/vs/editor/browser/widget/diffEditorWidget2/unchangedRanges.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ export class UnchangedRangesFeature extends Disposable {
2828
const m = this._diffModel.get();
2929
transaction(tx => {
3030
for (const s of this._originalEditor.getSelections() || []) {
31-
m?.revealOriginalLine(s.getStartPosition().lineNumber, tx);
32-
m?.revealOriginalLine(s.getEndPosition().lineNumber, tx);
31+
m?.ensureOriginalLineIsVisible(s.getStartPosition().lineNumber, tx);
32+
m?.ensureOriginalLineIsVisible(s.getEndPosition().lineNumber, tx);
3333
}
3434
});
3535
}));
@@ -38,8 +38,8 @@ export class UnchangedRangesFeature extends Disposable {
3838
const m = this._diffModel.get();
3939
transaction(tx => {
4040
for (const s of this._modifiedEditor.getSelections() || []) {
41-
m?.revealModifiedLine(s.getStartPosition().lineNumber, tx);
42-
m?.revealModifiedLine(s.getEndPosition().lineNumber, tx);
41+
m?.ensureModifiedLineIsVisible(s.getStartPosition().lineNumber, tx);
42+
m?.ensureModifiedLineIsVisible(s.getEndPosition().lineNumber, tx);
4343
}
4444
});
4545
}));

src/vs/editor/common/diff/linesDiffComputer.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ export class LineRangeMapping {
102102
public get changedLineCount() {
103103
return Math.max(this.originalRange.length, this.modifiedRange.length);
104104
}
105+
106+
public flip(): LineRangeMapping {
107+
return new LineRangeMapping(this.modifiedRange, this.originalRange, this.innerChanges?.map(c => c.flip()));
108+
}
105109
}
106110

107111
/**
@@ -130,6 +134,10 @@ export class RangeMapping {
130134
public toString(): string {
131135
return `{${this.originalRange.toString()}->${this.modifiedRange.toString()}}`;
132136
}
137+
138+
public flip(): RangeMapping {
139+
return new RangeMapping(this.modifiedRange, this.originalRange);
140+
}
133141
}
134142

135143
export class SimpleLineRangeMapping {
@@ -142,6 +150,10 @@ export class SimpleLineRangeMapping {
142150
public toString(): string {
143151
return `{${this.originalRange.toString()}->${this.modifiedRange.toString()}}`;
144152
}
153+
154+
public flip(): SimpleLineRangeMapping {
155+
return new SimpleLineRangeMapping(this.modifiedRange, this.originalRange);
156+
}
145157
}
146158

147159
export class MovedText {
@@ -161,4 +173,8 @@ export class MovedText {
161173
this.lineRangeMapping = lineRangeMapping;
162174
this.changes = changes;
163175
}
176+
177+
public flip(): MovedText {
178+
return new MovedText(this.lineRangeMapping.flip(), this.changes.map(c => c.flip()));
179+
}
164180
}

src/vs/monaco.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2501,6 +2501,7 @@ declare namespace monaco.editor {
25012501
constructor(originalRange: LineRange, modifiedRange: LineRange, innerChanges: RangeMapping[] | undefined);
25022502
toString(): string;
25032503
get changedLineCount(): any;
2504+
flip(): LineRangeMapping;
25042505
}
25052506

25062507
/**
@@ -2517,6 +2518,7 @@ declare namespace monaco.editor {
25172518
readonly modifiedRange: Range;
25182519
constructor(originalRange: Range, modifiedRange: Range);
25192520
toString(): string;
2521+
flip(): RangeMapping;
25202522
}
25212523

25222524
export class MovedText {
@@ -2528,13 +2530,15 @@ declare namespace monaco.editor {
25282530
*/
25292531
readonly changes: readonly LineRangeMapping[];
25302532
constructor(lineRangeMapping: SimpleLineRangeMapping, changes: readonly LineRangeMapping[]);
2533+
flip(): MovedText;
25312534
}
25322535

25332536
export class SimpleLineRangeMapping {
25342537
readonly originalRange: LineRange;
25352538
readonly modifiedRange: LineRange;
25362539
constructor(originalRange: LineRange, modifiedRange: LineRange);
25372540
toString(): string;
2541+
flip(): SimpleLineRangeMapping;
25382542
}
25392543
export interface IDimension {
25402544
width: number;

0 commit comments

Comments
 (0)