@@ -7,25 +7,30 @@ import { RunOnceScheduler } from 'vs/base/common/async';
7
7
import { Disposable } from 'vs/base/common/lifecycle' ;
8
8
import { IObservable , IReader , ITransaction , derived , observableSignal , observableSignalFromEvent , observableValue , transaction } from 'vs/base/common/observable' ;
9
9
import { autorunWithStore2 } from 'vs/base/common/observableImpl/autorun' ;
10
+ import { isDefined } from 'vs/base/common/types' ;
10
11
import { LineRange } from 'vs/editor/common/core/lineRange' ;
11
12
import { Range } from 'vs/editor/common/core/range' ;
12
13
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' ;
14
15
import { lineRangeMappingFromRangeMappings } from 'vs/editor/common/diff/standardLinesDiffComputer' ;
15
16
import { IDiffEditorModel } from 'vs/editor/common/editorCommon' ;
16
17
import { ITextModel } from 'vs/editor/common/model' ;
17
18
import { TextEditInfo } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper' ;
18
19
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' ;
20
21
21
22
export class DiffModel extends Disposable {
22
23
private readonly _isDiffUpToDate = observableValue < boolean > ( 'isDiffUpToDate' , false ) ;
23
24
public readonly isDiffUpToDate : IObservable < boolean > = this . _isDiffUpToDate ;
24
25
26
+ private _lastDiff : IDocumentDiff | undefined ;
25
27
private readonly _diff = observableValue < DiffState | undefined > ( 'diff' , undefined ) ;
26
28
public readonly diff : IObservable < DiffState | undefined > = this . _diff ;
27
29
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
+ ) ;
29
34
public readonly unchangedRegions : IObservable < UnchangedRegion [ ] > = derived ( 'unchangedRegions' , r =>
30
35
this . _hideUnchangedRegions . read ( r ) ? this . _unchangedRegions . read ( r ) . regions : [ ]
31
36
) ;
@@ -50,23 +55,28 @@ export class DiffModel extends Disposable {
50
55
if ( ! diff ) {
51
56
return ;
52
57
}
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
+
58
66
debouncer . schedule ( ) ;
59
67
} ) ) ;
60
68
this . _register ( model . original . onDidChangeContent ( ( e ) => {
61
69
const diff = this . _diff . get ( ) ;
62
70
if ( ! diff ) {
63
71
return ;
64
72
}
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
+ }
70
80
debouncer . schedule ( ) ;
71
81
} ) ) ;
72
82
@@ -137,8 +147,11 @@ export class DiffModel extends Disposable {
137
147
) ;
138
148
139
149
transaction ( tx => {
150
+ this . _lastDiff = result ;
140
151
this . _diff . set ( DiffState . fromDiffResult ( result ) , tx ) ;
141
152
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 ) ;
142
155
143
156
this . _unchangedRegions . set (
144
157
{
@@ -152,7 +165,7 @@ export class DiffModel extends Disposable {
152
165
} ) ) ;
153
166
}
154
167
155
- public revealModifiedLine ( lineNumber : number , tx : ITransaction ) : void {
168
+ public ensureModifiedLineIsVisible ( lineNumber : number , tx : ITransaction ) : void {
156
169
const unchangedRegions = this . _unchangedRegions . get ( ) . regions ;
157
170
for ( const r of unchangedRegions ) {
158
171
if ( r . getHiddenModifiedRange ( undefined ) . contains ( lineNumber ) ) {
@@ -162,7 +175,7 @@ export class DiffModel extends Disposable {
162
175
}
163
176
}
164
177
165
- public revealOriginalLine ( lineNumber : number , tx : ITransaction ) : void {
178
+ public ensureOriginalLineIsVisible ( lineNumber : number , tx : ITransaction ) : void {
166
179
const unchangedRegions = this . _unchangedRegions . get ( ) . regions ;
167
180
for ( const r of unchangedRegions ) {
168
181
if ( r . getHiddenOriginalRange ( undefined ) . contains ( lineNumber ) ) {
@@ -315,47 +328,82 @@ function applyOriginalEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], orig
315
328
return diff ;
316
329
}
317
330
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
+ }
323
335
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
+ }
325
344
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
+ }
332
349
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 ) ;
338
351
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 ) ;
344
359
345
360
return {
346
361
identical : false ,
347
362
quitEarly : false ,
348
363
changes,
349
- moves : [ ] ,
364
+ moves,
350
365
} ;
351
366
}
352
367
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
+ }
356
400
}
357
401
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 (
359
407
positionToLength ( c . originalRange . getStartPosition ( ) ) ,
360
408
positionToLength ( c . originalRange . getEndPosition ( ) ) ,
361
409
lengthOfRange ( c . modifiedRange ) . toLength ( ) ,
@@ -376,16 +424,10 @@ function applyModifiedEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], orig
376
424
) ;
377
425
} ) ;
378
426
379
- const changes = lineRangeMappingFromRangeMappings (
427
+ const newChanges = lineRangeMappingFromRangeMappings (
380
428
rangeMappings ,
381
429
originalTextModel . getLinesContent ( ) ,
382
430
modifiedTextModel . getLinesContent ( ) ,
383
431
) ;
384
-
385
- return {
386
- identical : false ,
387
- quitEarly : false ,
388
- changes,
389
- moves : [ ] ,
390
- } ;
432
+ return newChanges ;
391
433
}
0 commit comments